shithub: m8c

Download patch

ref: 566f3157e9d9754f352bf166d6e0514d359a477a
parent: db93668c5c0d4155cc7c32b25b95bf0bdc362ab1
author: laamaa <jonne.kokkonen@gmail.com>
date: Thu Sep 4 09:02:45 EDT 2025

add a toggleable message console

--- a/README.md
+++ b/README.md
@@ -156,6 +156,7 @@
 * Esc = toggle keyjazz on/off
 * r / select+start+opt+edit = reset display (if glitches appear on the screen, use this)
 * F12 = toggle audio routing on / off
+* ` (grave) = toggle in-app log overlay (configurable)
 
 ### Keyjazz
 
@@ -209,6 +210,14 @@
 
 This looks for a config file with the given name in the same directory as the default config. If you specify a config
 file that does not exist, a new default config file with the specified name will be created, which you can then edit.
+
+### Log overlay
+
+An in-app log overlay is available for platforms where reading console output is inconvenient.
+
+- Default toggle key: ` (grave). You can change it in `config.ini` under `[keyboard]` using `key_toggle_log=<SDL_SCANCODE>`.
+- The overlay shows recent `SDL_Log*` messages.
+- Long lines are wrapped to fit; the view tails the most recent output.
 
 Enjoy making some nice music!
 
--- a/src/config.c
+++ b/src/config.c
@@ -33,8 +33,8 @@
   c.idle_ms = 10;        // default to high performance
   c.wait_for_device = 1; // default to exit if device disconnected
   c.wait_packets = 256;  // amount of empty command queue reads before assuming device disconnected
-  c.audio_enabled = 0;        // route M8 audio to default output
-  c.audio_buffer_size = 0; // requested audio buffer size in samples: 0 = let SDL decide
+  c.audio_enabled = 0;   // route M8 audio to default output
+  c.audio_buffer_size = 0;    // requested audio buffer size in samples: 0 = let SDL decide
   c.audio_device_name = NULL; // Use this device, leave NULL to use the default output device
 
   c.key_up = SDL_SCANCODE_UP;
@@ -56,6 +56,7 @@
   c.key_jazz_inc_velocity = SDL_SCANCODE_KP_MINUS;
   c.key_jazz_dec_velocity = SDL_SCANCODE_KP_PLUS;
   c.key_toggle_audio = SDL_SCANCODE_F12;
+  c.key_toggle_log = SDL_SCANCODE_GRAVE; // default to ` key
 
   c.gamepad_up = SDL_GAMEPAD_BUTTON_DPAD_UP;
   c.gamepad_left = SDL_GAMEPAD_BUTTON_DPAD_LEFT;
@@ -90,8 +91,8 @@
 
   SDL_Log("Writing config file to %s", config_path);
 
-  #define INI_LINE_COUNT 50
-  #define INI_LINE_LENGTH 50
+#define INI_LINE_COUNT 50
+#define INI_LINE_LENGTH 50
 
   // Entries for the config file
   char ini_values[INI_LINE_COUNT][INI_LINE_LENGTH];
@@ -108,11 +109,13 @@
   snprintf(ini_values[initPointer++], INI_LINE_LENGTH, "[audio]\n");
   snprintf(ini_values[initPointer++], INI_LINE_LENGTH, "audio_enabled=%s\n",
            conf->audio_enabled ? "true" : "false");
-  snprintf(ini_values[initPointer++], INI_LINE_LENGTH, "audio_buffer_size=%d\n", conf->audio_buffer_size);
+  snprintf(ini_values[initPointer++], INI_LINE_LENGTH, "audio_buffer_size=%d\n",
+           conf->audio_buffer_size);
   snprintf(ini_values[initPointer++], INI_LINE_LENGTH, "audio_device_name=%s\n",
            conf->audio_device_name ? conf->audio_device_name : "Default");
   snprintf(ini_values[initPointer++], INI_LINE_LENGTH, "[keyboard]\n");
-  snprintf(ini_values[initPointer++], INI_LINE_LENGTH, ";Ref: https://wiki.libsdl.org/SDL2/SDL_Scancode\n");
+  snprintf(ini_values[initPointer++], INI_LINE_LENGTH,
+           ";Ref: https://wiki.libsdl.org/SDL2/SDL_Scancode\n");
   snprintf(ini_values[initPointer++], INI_LINE_LENGTH, "key_up=%d\n", conf->key_up);
   snprintf(ini_values[initPointer++], INI_LINE_LENGTH, "key_left=%d\n", conf->key_left);
   snprintf(ini_values[initPointer++], INI_LINE_LENGTH, "key_down=%d\n", conf->key_down);
@@ -135,7 +138,9 @@
            conf->key_jazz_inc_velocity);
   snprintf(ini_values[initPointer++], INI_LINE_LENGTH, "key_jazz_dec_velocity=%d\n",
            conf->key_jazz_dec_velocity);
-  snprintf(ini_values[initPointer++], INI_LINE_LENGTH, "key_toggle_audio=%d\n", conf->key_toggle_audio);
+  snprintf(ini_values[initPointer++], INI_LINE_LENGTH, "key_toggle_audio=%d\n",
+           conf->key_toggle_audio);
+  snprintf(ini_values[initPointer++], INI_LINE_LENGTH, "key_toggle_log=%d\n", conf->key_toggle_log);
   snprintf(ini_values[initPointer++], INI_LINE_LENGTH, "[gamepad]\n");
   snprintf(ini_values[initPointer++], INI_LINE_LENGTH, "gamepad_up=%d\n", conf->gamepad_up);
   snprintf(ini_values[initPointer++], INI_LINE_LENGTH, "gamepad_left=%d\n", conf->gamepad_left);
@@ -285,6 +290,7 @@
   const char *key_jazz_inc_velocity = ini_get(ini, "keyboard", "key_jazz_inc_velocity");
   const char *key_jazz_dec_velocity = ini_get(ini, "keyboard", "key_jazz_dec_velocity");
   const char *key_toggle_audio = ini_get(ini, "keyboard", "key_toggle_audio");
+  const char *key_toggle_log = ini_get(ini, "keyboard", "key_toggle_log");
 
   if (key_up)
     conf->key_up = SDL_atoi(key_up);
@@ -324,6 +330,8 @@
     conf->key_jazz_dec_velocity = SDL_atoi(key_jazz_dec_velocity);
   if (key_toggle_audio)
     conf->key_toggle_audio = SDL_atoi(key_toggle_audio);
+  if (key_toggle_log)
+    conf->key_toggle_log = SDL_atoi(key_toggle_log);
 }
 
 void read_gamepad_config(const ini_t *ini, config_params_s *conf) {
--- a/src/config.h
+++ b/src/config.h
@@ -36,6 +36,7 @@
   unsigned int key_jazz_inc_velocity;
   unsigned int key_jazz_dec_velocity;
   unsigned int key_toggle_audio;
+  unsigned int key_toggle_log;
 
   int gamepad_up;
   int gamepad_left;
--- a/src/inprint2.c
+++ b/src/inprint2.c
@@ -19,15 +19,14 @@
 
 void inline_font_initialize(struct inline_font *font) {
 
-  selected_font_w = font->width;
-  selected_font_h = font->height;
-  selected_inline_font = font;
-
   if (inline_font != NULL) {
-    selected_font = inline_font;
     return;
   }
 
+  selected_font_w = font->width;
+  selected_font_h = font->height;
+  selected_inline_font = font;
+
   SDL_IOStream *font_bmp =
       SDL_IOFromConstMem(selected_inline_font->image_data, selected_inline_font->image_size);
 
@@ -119,8 +118,7 @@
 
     if (bgcolor != fgcolor) {
       SDL_SetRenderDrawColor(selected_renderer, (bgcolor & 0x00FF0000) >> 16,
-                             (bgcolor & 0x0000FF00) >> 8, bgcolor & 0x000000FF,
-                             0xFF);
+                             (bgcolor & 0x0000FF00) >> 8, bgcolor & 0x000000FF, 0xFF);
       bg_rect = d_rect;
       bg_rect.w = (float)selected_inline_font->glyph_x;
       bg_rect.h = (float)selected_inline_font->glyph_y;
--- a/src/input.c
+++ b/src/input.c
@@ -188,6 +188,12 @@
     return;
   }
 
+  // Toggle in-app log overlay using config-defined key
+  if (event->key.scancode == ctx->conf.key_toggle_log) {
+    renderer_toggle_log_overlay();
+    return;
+  }
+
   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);
@@ -280,8 +286,8 @@
  * @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) {
+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) {
@@ -301,7 +307,7 @@
  * @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 Sint16 value) {
   const config_params_s *conf = &ctx->conf;
   gamepad_state.analog_values[axis] = value;
 
--- a/src/main.c
+++ b/src/main.c
@@ -18,7 +18,8 @@
 #include "gamepads.h"
 #include "render.h"
 
-// On MacOS TARGET_OS_IOS is defined as 0, so make sure that it's consistent on other platforms as well
+// On MacOS TARGET_OS_IOS is defined as 0, so make sure that it's consistent on other platforms as
+// well
 #ifndef TARGET_OS_IOS
 #define TARGET_OS_IOS 0
 #endif
@@ -95,7 +96,7 @@
 
   if (TARGET_OS_IOS == 1) {
     // Predefined settings for iOS
-    conf.init_fullscreen=1;
+    conf.init_fullscreen = 1;
   } else {
     // On other platforms, read config normally
     config_read(&conf);
@@ -165,6 +166,9 @@
 // Initialize the app: initialize context, configs, renderer controllers and attempt to find M8
 SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
   char *config_filename = NULL;
+
+  // Initialize in-app log capture/overlay
+  renderer_log_init();
 
   // Process the application's main callback roughly at 120 Hz
   SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "120");
--- a/src/render.c
+++ b/src/render.c
@@ -23,6 +23,7 @@
 static SDL_Renderer *rend;
 static SDL_Texture *main_texture;
 static SDL_Texture *hd_texture = NULL;
+static SDL_Texture *log_texture = NULL;
 static SDL_Color global_background_color = (SDL_Color){.r = 0x00, .g = 0x00, .b = 0x00, .a = 0x00};
 static SDL_RendererLogicalPresentation window_scaling_mode = SDL_LOGICAL_PRESENTATION_INTEGER_SCALE;
 static SDL_ScaleMode texture_scaling_mode = SDL_SCALEMODE_NEAREST;
@@ -35,6 +36,21 @@
 static int text_offset_y = 0;
 static int waveform_max_height = 24;
 
+// Log overlay state
+static int log_overlay_visible = 0;
+static int log_overlay_needs_redraw = 0;
+
+// Log buffer configuration
+#define LOG_BUFFER_MAX_LINES 512
+#define LOG_LINE_MAX_CHARS 256
+static char log_lines[LOG_BUFFER_MAX_LINES][LOG_LINE_MAX_CHARS];
+static int log_line_start = 0; // index of the oldest line
+static int log_line_count = 0; // number of valid lines
+
+// Previous SDL log output forwarding
+static SDL_LogOutputFunction prev_log_output_fn = NULL;
+static void *prev_log_output_userdata = NULL;
+
 static int texture_width = 320;
 static int texture_height = 240;
 static int hd_texture_width, hd_texture_height = 0;
@@ -93,7 +109,8 @@
   // Calculate the HD texture size
   const int new_hd_texture_width = texture_width * scale_factor;
   const int new_hd_texture_height = texture_height * scale_factor;
-  if (hd_texture != NULL && new_hd_texture_width == hd_texture_width && new_hd_texture_height == hd_texture_height) {
+  if (hd_texture != NULL && new_hd_texture_width == hd_texture_width &&
+      new_hd_texture_height == hd_texture_height) {
     // Texture exists and there is no change in the size, carry on
     SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "HD texture size not changed, skipping");
     return;
@@ -102,8 +119,8 @@
   hd_texture_width = new_hd_texture_width;
   hd_texture_height = new_hd_texture_height;
 
-  SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "Creating HD texture, scale factor: %d, size: %dx%d", scale_factor,
-               hd_texture_width, hd_texture_height);
+  SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "Creating HD texture, scale factor: %d, size: %dx%d",
+               scale_factor, hd_texture_width, hd_texture_height);
 
   // Destroy any existing HD texture
   if (hd_texture != NULL) {
@@ -132,6 +149,164 @@
   inline_font_initialize(font);
 }
 
+// Append a formatted line to the circular log buffer
+static void log_buffer_append_line(const char *line) {
+  if (line == NULL || line[0] == '\0') {
+    return;
+  }
+  const int index = (log_line_start + log_line_count) % LOG_BUFFER_MAX_LINES;
+  SDL_strlcpy(log_lines[index], line, LOG_LINE_MAX_CHARS);
+  if (log_line_count < LOG_BUFFER_MAX_LINES) {
+    log_line_count++;
+  } else {
+    log_line_start = (log_line_start + 1) % LOG_BUFFER_MAX_LINES;
+  }
+  log_overlay_needs_redraw = 1;
+}
+
+// SDL log output function that mirrors to our in-app buffer in addition to the default handler
+static void sdl_log_capture(void *userdata, int category, SDL_LogPriority priority,
+                            const char *message) {
+  (void)userdata;
+  (void)category;
+  (void)priority;
+
+  char formatted[LOG_LINE_MAX_CHARS];
+  SDL_snprintf(formatted, sizeof(formatted), ">%s", message ? message : "");
+
+  // Copy the formatted message into our buffer
+  log_buffer_append_line(formatted);
+
+  // Forward to the previous output function so messages still hit the console
+  if (prev_log_output_fn != NULL) {
+    prev_log_output_fn(prev_log_output_userdata, category, priority, message);
+  }
+}
+
+void renderer_log_init(void) {
+  // Preserve the existing output function and install our capture wrapper
+  SDL_GetLogOutputFunction(&prev_log_output_fn, &prev_log_output_userdata);
+  SDL_SetLogOutputFunction(sdl_log_capture, NULL);
+}
+
+void renderer_toggle_log_overlay(void) {
+  log_overlay_visible = !log_overlay_visible;
+  // Force redraw next present
+  dirty = 1;
+  log_overlay_needs_redraw = 1;
+}
+
+// Render the log buffer into a texture for overlay display
+static void render_log_overlay_texture(void) {
+  if (!log_overlay_visible) {
+    return;
+  }
+  if (log_texture == NULL) {
+    log_texture = SDL_CreateTexture(rend, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET,
+                                    texture_width, texture_height);
+    if (log_texture == NULL) {
+      SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Couldn't create log texture: %s", SDL_GetError());
+      return;
+    }
+    // Ensure overlay blends and scales consistently with the main texture
+    SDL_SetTextureBlendMode(log_texture, SDL_BLENDMODE_BLEND);
+    SDL_SetTextureScaleMode(log_texture, texture_scaling_mode);
+  }
+
+  if (!log_overlay_needs_redraw) {
+    return;
+  }
+  log_overlay_needs_redraw = 0;
+
+  SDL_Texture *prev_target = SDL_GetRenderTarget(rend);
+  SDL_SetRenderTarget(rend, log_texture);
+
+  // Semi-transparent background rectangle
+  SDL_SetRenderDrawColor(rend, 0, 0, 0, 220);
+  SDL_RenderClear(rend);
+
+  // Use small font to fit more lines
+  const int prev_font_mode = font_mode;
+  inline_font_close();
+  inline_font_initialize(fonts[0]);
+
+  const int line_height = fonts[0]->glyph_y + 1;
+  const int margin_x = 2;
+  const int usable_width = texture_width - (margin_x * 2);
+  const int cols = SDL_max(1, usable_width / (fonts[0]->glyph_x + 1));
+
+  const Uint32 fg = 0xFFFFFF; // light grey
+  const Uint32 bg = 0xFFFFFF; // inprint translates same bg as fg to transparent
+
+  // Compute how many text rows fit
+  const int max_rows = texture_height / line_height - 1;
+  int rows_needed = max_rows;
+
+  // Determine start line and character offset so the overlay shows the most recent rows
+  int newest_idx =
+      (log_line_start + log_line_count - 1 + LOG_BUFFER_MAX_LINES) % LOG_BUFFER_MAX_LINES;
+  int start_idx = log_line_start;
+  size_t start_char_offset = 0;
+
+  if (log_line_count > 0) {
+    for (int n = 0; n < log_line_count && rows_needed > 0; n++) {
+      const int idx = (newest_idx - n + LOG_BUFFER_MAX_LINES) % LOG_BUFFER_MAX_LINES;
+      const size_t len = SDL_strlen(log_lines[idx]);
+      const int rows_for_line = SDL_max(1, (int)((len + cols - 1) / cols));
+      if (rows_for_line >= rows_needed) {
+        int offset = (int)len - (rows_needed * cols);
+        if (offset < 0) {
+          offset = 0;
+        }
+        start_idx = idx;
+        start_char_offset = (size_t)offset;
+        rows_needed = 0;
+        break;
+      } else {
+        rows_needed -= rows_for_line;
+        start_idx = idx;
+        start_char_offset = 0;
+      }
+    }
+  }
+
+  // Render forward from the computed start to the newest
+  int y = 0;
+  if (log_line_count > 0) {
+    int cur = start_idx;
+    const int last = newest_idx;
+    size_t offset = start_char_offset;
+    while (1) {
+      const char *s = log_lines[cur];
+      const size_t len = SDL_strlen(s);
+      for (size_t pos = offset; pos < len && y < texture_height;) {
+        size_t remaining = len - pos;
+        size_t take = (size_t)cols < remaining ? (size_t)cols : remaining;
+        char buf[LOG_LINE_MAX_CHARS];
+        if (take >= sizeof(buf)) {
+          take = sizeof(buf) - 1;
+        }
+        SDL_memcpy(buf, s + pos, take);
+        buf[take] = '\0';
+        inprint(rend, buf, margin_x, y, fg, bg);
+        y += line_height;
+        pos += take;
+      }
+      if (cur == last || y >= texture_height) {
+        break;
+      }
+      cur = (cur + 1) % LOG_BUFFER_MAX_LINES;
+      offset = 0;
+    }
+  }
+
+  // Restore previous font
+  inline_font_close();
+  inline_font_initialize(fonts[prev_font_mode]);
+
+  SDL_SetRenderTarget(rend, prev_target);
+}
+
 static void check_and_adjust_window_and_texture_size(const int new_width, const int new_height) {
 
   if (texture_width == new_width && texture_height == new_height) {
@@ -159,6 +334,12 @@
     SDL_DestroyTexture(main_texture);
   }
 
+  // Drop log texture so it can be recreated with correct size
+  if (log_texture != NULL) {
+    SDL_DestroyTexture(log_texture);
+    log_texture = NULL;
+  }
+
   main_texture = SDL_CreateTexture(rend, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET,
                                    texture_width, texture_height);
   SDL_SetTextureScaleMode(main_texture, texture_scaling_mode);
@@ -206,6 +387,9 @@
   if (hd_texture != NULL) {
     SDL_DestroyTexture(hd_texture);
   }
+  if (log_texture != NULL) {
+    SDL_DestroyTexture(log_texture);
+  }
   SDL_DestroyRenderer(rend);
   SDL_DestroyWindow(win);
 }
@@ -421,7 +605,8 @@
   renderer_set_font_mode(0);
 
   SDL_SetHint(SDL_HINT_IOS_HIDE_HOME_INDICATOR, "1");
-  renderer_fix_texture_scaling_after_window_resize(conf); // iOS needs this, doesn't hurt on others either
+  renderer_fix_texture_scaling_after_window_resize(
+      conf); // iOS needs this, doesn't hurt on others either
 
   dirty = 1;
 
@@ -527,6 +712,14 @@
     } else {
       // Window and texture aspect ratios match
       SDL_RenderTexture(rend, hd_texture, NULL, NULL);
+    }
+  }
+
+  // Ensure log overlay is up to date, then composite it if visible before present
+  if (log_overlay_visible) {
+    render_log_overlay_texture();
+    if (log_texture) {
+      SDL_RenderTexture(rend, log_texture, NULL, NULL);
     }
   }
 
--- a/src/render.h
+++ b/src/render.h
@@ -27,6 +27,10 @@
 
 void show_error_message(const char *message);
 
+// Log overlay controls
+void renderer_log_init(void);
+void renderer_toggle_log_overlay(void);
+
 int screensaver_init(void);
 void screensaver_draw(void);
 void screensaver_destroy(void);
--