shithub: m8c

Download patch

ref: 61fe9fb2c3a8b3bfa3c7247978ad60e0a6fa4fdd
parent: 8d3fade26a10b6fe03f543ded533890c1655c0df
author: Jonne Kokkonen <jonne.kokkonen@gmail.com>
date: Thu Apr 17 19:18:45 EDT 2025

Start working on event based gamepad input system

--- a/src/common.h
+++ b/src/common.h
@@ -2,7 +2,7 @@
 #define COMMON_H_
 #include "config.h"
 
-enum app_state { QUIT, WAIT_FOR_DEVICE, RUN };
+enum app_state { QUIT, INITIALIZE, WAIT_FOR_DEVICE, RUN };
 
 struct app_context {
     config_params_s conf;
--- a/src/events.c
+++ b/src/events.c
@@ -19,7 +19,7 @@
     ret_val = SDL_APP_SUCCESS;
     break;
   case SDL_EVENT_DID_ENTER_BACKGROUND:
-    // iOS: Application entered into background on iOS. About 5 seconds to stop things.
+    // iOS: Application entered into the background on iOS. About 5 seconds to stop things.
     SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Received SDL_EVENT_DID_ENTER_BACKGROUND");
     ctx->app_suspended = 1;
     if (ctx->device_connected)
@@ -26,10 +26,10 @@
       m8_pause_processing();
     break;
   case SDL_EVENT_WILL_ENTER_BACKGROUND:
-    // iOS: App about to enter into background
+    // iOS: App about to enter into the background
     break;
   case SDL_EVENT_WILL_ENTER_FOREGROUND:
-    // iOS: App returning to foreground
+    // iOS: App returning to the foreground
     SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Received SDL_EVENT_WILL_ENTER_FOREGROUND");
     break;
   case SDL_EVENT_DID_ENTER_FOREGROUND:
@@ -42,7 +42,7 @@
     }
   case SDL_EVENT_WINDOW_RESIZED:
   case SDL_EVENT_WINDOW_MOVED:
-    // If window size is changed, some operating systems might need a little nudge to fix scaling
+    // If the window size is changed, some operating systems might need a little nudge to fix scaling
     renderer_fix_texture_scaling_after_window_resize();
     break;
 
@@ -60,6 +60,19 @@
   case SDL_EVENT_KEY_UP:
     input_handle_key_up_event(ctx, event);
     break;
+
+  case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
+    input_handle_gamepad_button(ctx, event->gbutton.button, true);
+    break;
+
+  case SDL_EVENT_GAMEPAD_BUTTON_UP:
+    input_handle_gamepad_button(ctx, event->gbutton.button, false);
+    break;
+
+  case SDL_EVENT_GAMEPAD_AXIS_MOTION:
+    input_handle_gamepad_axis(ctx, event->gaxis.axis, event->gaxis.value);
+    break;
+
 
   default:
     break;
--- a/src/gamecontrollers.c
+++ b/src/gamecontrollers.c
@@ -3,16 +3,30 @@
 //
 
 #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];
 
-// Opens available game controllers and returns the amount of opened 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);
@@ -22,8 +36,11 @@
   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[1024] = {0};
-  snprintf(db_filename, sizeof(db_filename), "%sgamecontrollerdb.txt", SDL_GetPrefPath("", "m8c"));
+  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) {
@@ -34,10 +51,12 @@
 
   if (db_rw != NULL) {
     const int mappings = SDL_AddGamepadMappingsFromIO(db_rw, true);
-    if (mappings != -1)
+    if (mappings != -1) {
       SDL_Log("Found %d game controller mappings", mappings);
-    else
+    } 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.");
   }
@@ -49,6 +68,10 @@
     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++;
@@ -65,87 +88,3 @@
       SDL_CloseGamepad(game_controllers[i]);
   }
 }
-
-// Check whether a button is pressed on a gamepad and return 1 if pressed.
-static int get_game_controller_button(const config_params_s *conf, SDL_Gamepad *controller,
-                                      const int button) {
-
-  const int button_mappings[8] = {conf->gamepad_up,     conf->gamepad_down, conf->gamepad_left,
-                                  conf->gamepad_right,  conf->gamepad_opt,  conf->gamepad_edit,
-                                  conf->gamepad_select, conf->gamepad_start};
-
-  // Check digital buttons
-  if (SDL_GetGamepadButton(controller, button_mappings[button])) {
-    return 1;
-  }
-
-  // If digital button isn't pressed, check the corresponding analog control
-  switch (button) {
-  case INPUT_UP:
-    return SDL_GetGamepadAxis(controller, conf->gamepad_analog_axis_updown) <
-           -conf->gamepad_analog_threshold;
-  case INPUT_DOWN:
-    return SDL_GetGamepadAxis(controller, conf->gamepad_analog_axis_updown) >
-           conf->gamepad_analog_threshold;
-  case INPUT_LEFT:
-    return SDL_GetGamepadAxis(controller, conf->gamepad_analog_axis_leftright) <
-           -conf->gamepad_analog_threshold;
-  case INPUT_RIGHT:
-    return SDL_GetGamepadAxis(controller, conf->gamepad_analog_axis_leftright) >
-           conf->gamepad_analog_threshold;
-  case INPUT_OPT:
-    return SDL_GetGamepadAxis(controller, conf->gamepad_analog_axis_opt) >
-           conf->gamepad_analog_threshold;
-  case INPUT_EDIT:
-    return SDL_GetGamepadAxis(controller, conf->gamepad_analog_axis_edit) >
-           conf->gamepad_analog_threshold;
-  case INPUT_SELECT:
-    return SDL_GetGamepadAxis(controller, conf->gamepad_analog_axis_select) >
-           conf->gamepad_analog_threshold;
-  case INPUT_START:
-    return SDL_GetGamepadAxis(controller, conf->gamepad_analog_axis_start) >
-           conf->gamepad_analog_threshold;
-  default:
-    return 0;
-  }
-}
-
-// Handle game controllers, simply check all buttons and analog axis on every
-// cycle
-int gamecontrollers_handle_buttons(const config_params_s *conf) {
-
-  const int keycodes[8] = {key_up,  key_down, key_left,   key_right,
-                           key_opt, key_edit, key_select, key_start};
-
-  int key = 0;
-
-  // Cycle through every active game controller
-  for (int gc = 0; gc < num_joysticks; gc++) {
-    // Cycle through all M8 buttons
-    for (int button = 0; button < INPUT_MAX; button++) {
-      // If the button is active, add the keycode to the variable containing
-      // active keys
-      if (get_game_controller_button(conf, game_controllers[gc], button)) {
-        key |= keycodes[button];
-      }
-    }
-  }
-
-  return key;
-}
-
-input_msg_s gamecontrollers_handle_special_messages(const config_params_s *conf) {
-  input_msg_s msg = {0};
-  // Read special case game controller buttons quit and reset
-  for (int gc = 0; gc < num_joysticks; gc++) {
-    if (SDL_GetGamepadButton(game_controllers[gc], conf->gamepad_quit) &&
-        (SDL_GetGamepadButton(game_controllers[gc], conf->gamepad_select) ||
-         SDL_GetGamepadAxis(game_controllers[gc], conf->gamepad_analog_axis_select)))
-      msg = (input_msg_s){special, msg_quit, 0};
-    else if (SDL_GetGamepadButton(game_controllers[gc], conf->gamepad_reset) &&
-             (SDL_GetGamepadButton(game_controllers[gc], conf->gamepad_select) ||
-              SDL_GetGamepadAxis(game_controllers[gc], conf->gamepad_analog_axis_select)))
-      msg = (input_msg_s){special, msg_reset_display, 0};
-  }
-  return msg;
-}
\ No newline at end of file
--- a/src/gamecontrollers.h
+++ b/src/gamecontrollers.h
@@ -8,8 +8,6 @@
 #include "config.h"
 #include "input.h"
 
-#define MAX_CONTROLLERS 4
-
 int gamecontrollers_initialize();
 void gamecontrollers_close();
 int gamecontrollers_handle_buttons(const config_params_s *conf);
--- a/src/input.c
+++ b/src/input.c
@@ -15,6 +15,12 @@
 static unsigned char keycode = 0; // value of the pressed key
 static input_msg_s key = {normal, 0, 0};
 
+// Store gamepad state
+static struct {
+  int current_buttons;
+  int analog_values[SDL_GAMEPAD_AXIS_COUNT];
+} gamepad_state = {0};
+
 static unsigned char toggle_input_keyjazz() {
   keyjazz_enabled = !keyjazz_enabled;
   SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, keyjazz_enabled ? "Keyjazz enabled" : "Keyjazz disabled");
@@ -119,7 +125,7 @@
  * no match is found.
  */
 static input_msg_s handle_m8_keys(const SDL_Event *event, const config_params_s *conf) {
-  // Default message with normal type and no value
+  // Default message with a normal type and no value
   input_msg_s key = {normal, 0, 0};
 
   // Get the current scancode
@@ -145,14 +151,6 @@
       {conf->key_delete, key_opt | key_edit},
   };
 
-  // Handle special messages (different message type)
-  const struct {
-    SDL_Scancode scancode;
-    special_messages_t message;
-  } special_key_map[] = {
-      {conf->key_reset, msg_reset_display},
-  };
-
   // Check normal key mappings
   for (size_t i = 0; i < sizeof(normal_key_map) / sizeof(normal_key_map[0]); i++) {
     if (scancode == normal_key_map[i].scancode) {
@@ -161,29 +159,32 @@
     }
   }
 
-  // Check special key mappings
-  for (size_t i = 0; i < sizeof(special_key_map) / sizeof(special_key_map[0]); i++) {
-    if (scancode == special_key_map[i].scancode) {
-      key.type = special;
-      key.value = special_key_map[i].message;
-      return key;
-    }
-  }
-
-  // No matching key found, return default key message
+  // No matching key found, return the default key message
   return key;
 }
 
+/**
+ * Handles the key down events during the application runtime.
+ * Processes specific key inputs for actions such as toggling fullscreen,
+ * quitting the application, toggling audio, resetting display, or handling keyjazz
+ * and M8 key mappings. Integrates input settings based on the current context and input state.
+ *
+ * @param ctx Pointer to the app_context structure containing the current application
+ * context and configuration parameters.
+ * @param event Pointer to the SDL_Event structure containing data about the key down
+ * event, including key and modifier states.
+ */
 void input_handle_key_down_event(struct app_context *ctx, const SDL_Event *event) {
   if (event->key.repeat > 0) {
     return;
   }
+
   if (event->key.key == SDLK_RETURN && (event->key.mod & SDL_KMOD_ALT) > 0) {
     toggle_fullscreen();
     return;
   }
   if (event->key.key == SDLK_F4 && (event->key.mod & SDL_KMOD_ALT) > 0) {
-    key = (input_msg_s){special, msg_quit, 0};
+    ctx->app_state = QUIT;
     return;
   }
   if (event->key.key == SDLK_ESCAPE) {
@@ -191,11 +192,17 @@
     return;
   }
 
-  if (event->key.scancode == ctx->conf.key_toggle_audio) {
+  if (event->key.scancode == ctx->conf.key_toggle_audio && ctx->device_connected) {
     ctx->conf.audio_enabled = !ctx->conf.audio_enabled;
     audio_toggle(ctx->conf.audio_device_name, ctx->conf.audio_buffer_size);
+    return;
   }
 
+  if (event->key.scancode == ctx->conf.key_reset && ctx->device_connected) {
+    m8_reset_display();
+    return;
+  }
+
   key = handle_m8_keys(event, &ctx->conf);
   if (keyjazz_enabled) {
     key = handle_keyjazz(event, key.value, &ctx->conf);
@@ -205,6 +212,14 @@
   input_process_and_send(ctx);
 }
 
+/**
+ * Handles the "key up" SDL event and processes the associated input.
+ * Interprets the event based on key mappings (default and keyjazz mode) and updates the input
+ * state. Sends the processed input message for further handling.
+ *
+ * @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) {
   key = handle_m8_keys(event, &ctx->conf);
   if (keyjazz_enabled) {
@@ -215,9 +230,102 @@
   input_process_and_send(ctx);
 }
 
-int input_process_and_send(struct app_context *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;
+  int key_value = 0;
+
+  // Handle standard buttons
+  const struct {
+    int button;
+    unsigned char value;
+  } normal_key_map[] = {
+      {conf->gamepad_up, key_up},         {conf->gamepad_left, key_left},
+      {conf->gamepad_down, key_down},     {conf->gamepad_right, key_right},
+      {conf->gamepad_select, key_select}, {conf->gamepad_start, key_start},
+      {conf->gamepad_opt, key_opt},       {conf->gamepad_edit, key_edit},
+  };
+
+  // Check normal key mappings
+  for (size_t i = 0; i < sizeof(normal_key_map) / sizeof(normal_key_map[0]); i++) {
+    if (button == normal_key_map[i].button) {
+      key_value = normal_key_map[i].value;
+    }
+  }
+
+  if (pressed) {
+    gamepad_state.current_buttons |= key_value;
+  } else {
+    gamepad_state.current_buttons &= ~key_value;
+  }
+
+  // Handle special button combinations
+  if (gamepad_state.current_buttons == (key_start | key_select | key_opt | key_edit)) {
+    m8_reset_display();
+    return;
+  }
+
+  if (pressed && button == conf->gamepad_quit && gamepad_state.current_buttons == key_select) {
+    ctx->app_state = QUIT;
+    return;
+  }
+
+  input_process_and_send(ctx);
+}
+
+/**
+ * Helper function to update button states based on analog axis value.
+ *
+ * @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
+ */
+static void update_button_state_from_axis(Sint16 axis_value, int threshold,
+                                         int negative_key, int positive_key) {
+  if (axis_value < -threshold) {
+    gamepad_state.current_buttons |= negative_key;
+  } else if (axis_value > threshold) {
+    gamepad_state.current_buttons |= positive_key;
+  } else {
+    gamepad_state.current_buttons &= ~(negative_key | positive_key);
+  }
+}
+
+
+void input_handle_gamepad_axis(const struct app_context *ctx, const SDL_GamepadAxis axis,
+                             const Sint16 value) {
+  const config_params_s *conf = &ctx->conf;
+  gamepad_state.analog_values[axis] = value;
+
+  // Process directional axes and update button states
+  if (axis == conf->gamepad_analog_axis_updown) {
+    update_button_state_from_axis(value, conf->gamepad_analog_threshold, key_up, key_down);
+  } else if (axis == conf->gamepad_analog_axis_leftright) {
+    update_button_state_from_axis(value, conf->gamepad_analog_threshold, key_left, key_right);
+  } else if (axis == conf->gamepad_analog_axis_select) {
+    update_button_state_from_axis(value, conf->gamepad_analog_threshold, key_select, key_select);
+  } else if (axis == conf->gamepad_analog_axis_opt) {
+    update_button_state_from_axis(value, conf->gamepad_analog_threshold, key_opt, key_opt);
+  } else if (axis == conf->gamepad_analog_axis_start) {
+    update_button_state_from_axis(value, conf->gamepad_analog_threshold, key_start, key_start);
+  } else if (axis == conf->gamepad_analog_axis_edit) {
+    update_button_state_from_axis(value, conf->gamepad_analog_threshold, key_edit, key_edit);
+  }
+}
+
+
+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};
 
@@ -239,22 +347,7 @@
     }
     prev_input = input.value;
     break;
-  case special:
-    if (input.value != prev_input) {
-      prev_input = input.value;
-      switch (input.value) {
-      case msg_quit:
-        SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Received msg_quit from input device.");
-        ctx->app_state = QUIT;
-        break;
-      case msg_reset_display:
-        m8_reset_display();
-        break;
-      default:
-        break;
-      }
-      break;
-    }
+  default:;
   }
   return 1;
 }
\ No newline at end of file
--- a/src/input.h
+++ b/src/input.h
@@ -36,9 +36,7 @@
 typedef enum input_type_t { normal, keyjazz, special } input_type_t;
 
 typedef enum special_messages_t {
-  msg_quit = 1,
-  msg_reset_display = 2,
-  msg_toggle_audio = 3
+  msg_reset_display = 2
 } special_messages_t;
 
 typedef struct input_msg_s {
@@ -48,8 +46,10 @@
 } input_msg_s;
 
 input_msg_s input_get_msg(config_params_s *conf);
-int input_process_and_send(struct app_context *ctx);
+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_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);
 
 #endif // INPUT_H
--- a/src/main.c
+++ b/src/main.c
@@ -16,8 +16,8 @@
 #include "backends/m8.h"
 #include "common.h"
 #include "config.h"
-#include "gamecontrollers.h"
 #include "events.h"
+#include "gamecontrollers.h"
 #include "render.h"
 
 static void do_wait_for_device(struct app_context *ctx) {
@@ -54,9 +54,9 @@
         ctx->device_connected = 1;
         screensaver_destroy();
         screensaver_initialized = 0;
-        //renderer_clear_screen();
+        // renderer_clear_screen();
         SDL_Log("Device connected.");
-        SDL_Delay(100); // Give the device time to initialize
+        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.");
@@ -98,7 +98,16 @@
   return conf;
 }
 
-static unsigned char handle_device_initialization(const unsigned char wait_for_device,
+/**
+ * Handles the initialization of a device and verifies its connection state.
+ *
+ * @param wait_for_device A flag indicating whether the system should wait for the device to
+ * connect. If set to 0 and the device is not detected, the application exits.
+ * @param preferred_device A string representing the preferred device to initialize.
+ * @return An unsigned char indicating the connection state of the device.
+ *         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 unsigned char device_connected = m8_initialize(1, preferred_device);
   if (!wait_for_device && device_connected == 0) {
@@ -108,21 +117,26 @@
   return device_connected;
 }
 
-// Main callback loop - read inputs, process data from device, render screen
+// Main callback loop - read inputs, process data from the device, render screen
 SDL_AppResult SDL_AppIterate(void *appstate) {
+  if (appstate == NULL) {
+    return SDL_APP_FAILURE;
+  }
+
   struct app_context *ctx = appstate;
   SDL_AppResult app_result = SDL_APP_CONTINUE;
 
   switch (ctx->app_state) {
+  case INITIALIZE:
+    break;
 
-  case WAIT_FOR_DEVICE: {
+  case WAIT_FOR_DEVICE:
     if (ctx->conf.wait_for_device) {
       do_wait_for_device(ctx);
     }
     break;
-  }
 
-  case RUN: {
+  case RUN:
     const int result = m8_process_data(&ctx->conf);
     if (result == DEVICE_DISCONNECTED) {
       ctx->device_connected = 0;
@@ -133,13 +147,11 @@
     }
     render_screen();
     break;
-  }
 
-  case QUIT: {
+  case QUIT:
     app_result = SDL_APP_SUCCESS;
     break;
   }
-  }
 
   return app_result;
 }
@@ -154,11 +166,13 @@
     return SDL_APP_FAILURE;
   }
 
-  ctx->app_state = WAIT_FOR_DEVICE;
+  *appstate = ctx;
+  ctx->app_state = INITIALIZE;
 
   ctx->conf = initialize_config(argc, argv, &ctx->preferred_device, &config_filename);
   ctx->device_connected =
-      handle_device_initialization(ctx->conf.wait_for_device, ctx->preferred_device);
+      handle_m8_connection_init(ctx->conf.wait_for_device, ctx->preferred_device);
+
   if (!renderer_initialize(&ctx->conf)) {
     SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Failed to initialize renderer.");
     return SDL_APP_FAILURE;
@@ -170,29 +184,24 @@
   SDL_LogDebug(SDL_LOG_CATEGORY_TEST, "Running a Debug build");
 #endif
 
-  gamecontrollers_initialize();
+  if (gamecontrollers_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 120hz
+  // Process the application's main callback roughly at 120 Hz
   SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "120");
 
-  *appstate = ctx;
-
-  if (ctx->app_state == WAIT_FOR_DEVICE) {
-    if (!ctx->device_connected) {
-      ctx->device_connected =
-          handle_device_initialization(ctx->conf.wait_for_device, ctx->preferred_device);
+  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);
     }
-    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;
-      m8_reset_display(); // Avoid display glitches.
-    } else {
-      SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Device not detected.");
-      ctx->device_connected = 0;
-      ctx->app_state = ctx->conf.wait_for_device ? WAIT_FOR_DEVICE : QUIT;
-    }
+    ctx->app_state = RUN;
+    m8_reset_display(); // Avoid display glitches.
+  } else {
+    SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Device not detected.");
+    ctx->device_connected = 0;
+    ctx->app_state = ctx->conf.wait_for_device ? WAIT_FOR_DEVICE : QUIT;
   }
 
   return SDL_APP_CONTINUE;
--- a/src/render.c
+++ b/src/render.c
@@ -52,7 +52,7 @@
     texture_scaling_mode = SDL_SCALEMODE_NEAREST;
   } else {
     window_scaling_mode = SDL_LOGICAL_PRESENTATION_LETTERBOX;
-    texture_scaling_mode = SDL_SCALEMODE_LINEAR;
+    texture_scaling_mode = SDL_SCALEMODE_NEAREST;
   }
   renderer_fix_texture_scaling_after_window_resize();
 }
@@ -109,9 +109,7 @@
 
   renderer_set_font_mode(0);
 
-#ifdef TARGET_OS_IOS
   SDL_SetHint(SDL_HINT_IOS_HIDE_HOME_INDICATOR, "1");
-#endif
 
   dirty = 1;
 
@@ -152,7 +150,7 @@
   SDL_SetRenderTarget(rend, main_texture);
 }
 
-// Set M8 hardware model in use. 0 = MK1, 1 = MK2
+// Set the M8 hardware model in use. 0 = MK1, 1 = MK2
 void set_m8_model(const unsigned int model) {
 
   if (model == 1) {
@@ -199,7 +197,7 @@
   SDL_SetWindowFullscreen(win, fullscreen_state ? false : true);
   SDL_SyncWindow(win);
   if (fullscreen_state) {
-    // Show cursor when in windowed state
+    // Show cursor when in a windowed state
     SDL_ShowCursor();
   } else {
     SDL_HideCursor();
@@ -216,8 +214,8 @@
       command->background.r << 16 | command->background.g << 8 | command->background.b;
 
   /* Notes:
-     If large font is enabled, offset the screen elements by a fixed amount.
-     If background and foreground colors are the same, draw transparent
+     If a large font is enabled, offset the screen elements by a fixed amount.
+     If background and foreground colors are the same, draw a transparent
      background. Due to the font bitmaps, a different pixel offset is needed for
      both*/
 
@@ -290,7 +288,7 @@
 
     SDL_SetRenderDrawColor(rend, command->color.r, command->color.g, command->color.b, 255);
 
-    // Create a SDL_Point array of the waveform pixels for batch drawing
+    // Create an SDL_Point array of the waveform pixels for batch drawing
     SDL_FPoint waveform_points[command->waveform_size];
 
     for (int i = 0; i < command->waveform_size; i++) {
@@ -339,15 +337,31 @@
 void render_screen(void) {
   if (dirty) {
     dirty = 0;
-    SDL_SetRenderTarget(rend, NULL);
 
-    SDL_SetRenderDrawColor(rend, global_background_color.r, global_background_color.g,
-                           global_background_color.b, global_background_color.a);
+    if (!SDL_SetRenderTarget(rend, NULL)) {
+      SDL_LogCritical(SDL_LOG_CATEGORY_RENDER, "Couldn't set renderer target to window: %s", SDL_GetError());
+    }
 
-    SDL_RenderClear(rend);
-    SDL_RenderTexture(rend, main_texture, NULL, NULL);
-    SDL_RenderPresent(rend);
-    SDL_SetRenderTarget(rend, main_texture);
+    if (!SDL_SetRenderDrawColor(rend, global_background_color.r, global_background_color.g,
+                           global_background_color.b, global_background_color.a)) {
+      SDL_LogCritical(SDL_LOG_CATEGORY_RENDER, "Couldn't set render draw color: %s", SDL_GetError());
+    }
+
+    if (!SDL_RenderClear(rend)) {
+      SDL_LogCritical(SDL_LOG_CATEGORY_RENDER, "Couldn't clear renderer: %s", SDL_GetError());
+    }
+
+    if (!SDL_RenderTexture(rend, main_texture, NULL, NULL)) {
+      SDL_LogCritical(SDL_LOG_CATEGORY_RENDER, "Couldn't render texture: %s", SDL_GetError());
+    }
+
+    if (!SDL_RenderPresent(rend)) {
+      SDL_LogCritical(SDL_LOG_CATEGORY_RENDER, "Couldn't present renderer: %s", SDL_GetError());
+    }
+
+    if (!SDL_SetRenderTarget(rend, main_texture)) {
+      SDL_LogCritical(SDL_LOG_CATEGORY_RENDER, "Couldn't set renderer target to texture: %s", SDL_GetError());
+    }
 
     fps++;
 
--