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);
--
⑨