ref: 015637c0756837916374968c491df411ee5ddb9a
parent: f70b0dfb8b5d4d2d4e1b2f65f13d64b4323d0ded
parent: 3a71fc7783ba652072beacd37abce5197cf431a6
author: Jonne Kokkonen <jonne.kokkonen@gmail.com>
date: Fri Apr 18 19:07:35 EDT 2025
Merge pull request #193 from laamaa/sdl_callbacks Refactor application to use SDL main callback, implement sdl3 gamepad support
--- a/Makefile
+++ b/Makefile
@@ -1,51 +1,27 @@
-#Set all your object files (the object files of all the .c files in your project, e.g. main.o my_sub_functions.o )
-OBJ = src/main.o \
-src/command.o \
-src/config.o \
-src/fx_cube.o \
-src/gamecontrollers.o \
-src/ini.o \
-src/inprint2.o \
-src/input.o \
-src/backends/queue.o \
-src/render.o \
-src/backends/audio_sdl.o \
-src/backends/audio_libusb.o \
-src/backends/m8_libserialport.o \
-src/backends/m8_libusb.o \
-src/backends/m8_rtmidi.o \
-src/backends/ringbuffer.o \
-src/backends/slip.o \
+#Set the compiler you are using
+CC = gcc
-#Set any dependant header files so that if they are edited they cause a complete re-compile (e.g. main.h some_subfunctions.h some_definitions_file.h ), or leave blank
-DEPS = \
-src/command.h \
-src/config.h \
-src/fx_cube.h \
-src/gamecontrollers.h \
-src/ini.h \
-src/input.h \
-src/render.h \
-src/backends/audio.h \
-src/backends/m8.h \
-src/backends/ringbuffer.h \
-src/backends/queue.h \
-src/backends/slip.h \
-src/fonts/inline_font.h
+#Set the filename extension of your C files
+EXTENSION = .c
+# Location of the source files
+SOURCE_DIR = src/
+
+# Find all source files in the src directory and subdirectories
+SRC_FILES := $(shell find $(SOURCE_DIR) -type f -name "*$(EXTENSION)")
+
+# Convert to object files
+OBJ := $(SRC_FILES:.c=.o)
+
+# Find all header files for dependencies
+DEPS := $(shell find src -type f -name "*.h")
+
+
#Any special libraries you are using in your project (e.g. -lbcm2835 -lrt `pkg-config --libs gtk+-3.0` ), or leave blank
INCLUDES = $(shell pkg-config --libs sdl3 libserialport | sed 's/-mwindows//')
#Set any compiler flags you want to use (e.g. -I/usr/include/somefolder `pkg-config --cflags gtk+-3.0` ), or leave blank
local_CFLAGS = $(CFLAGS) $(shell pkg-config --cflags sdl3 libserialport) -DUSE_LIBSERIALPORT -Wall -Wextra -O2 -pipe -I. -DNDEBUG
-
-#Set the compiler you are using ( gcc for C or g++ for C++ )
-CC = gcc
-
-#Set the filename extensiton of your C files (e.g. .c or .cpp )
-EXTENSION = .c
-
-SOURCE_DIR = src/
#define a rule that applies to all files ending in the .o suffix, which says that the .o file depends upon the .c version of the file and all the .h files included in the DEPS macro. Compile each object file
%.o: %$(EXTENSION) $(DEPS)
--- a/src/backends/audio_sdl.c
+++ b/src/backends/audio_sdl.c
@@ -25,8 +25,17 @@
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 +66,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());
@@ -83,7 +94,7 @@
const char *device_name = SDL_GetAudioDeviceName(instance_id);
SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO, "%s", device_name);
if (SDL_strstr(device_name, "M8") != NULL) {- SDL_Log("M8 Audio Input device found: %s", device_name);+ SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "M8 Audio Input device found: %s", device_name);
m8_device_id = instance_id;
}
}
@@ -116,7 +127,8 @@
char audio_buffer_size_str[256];
SDL_snprintf(audio_buffer_size_str, sizeof(audio_buffer_size_str), "%d", audio_buffer_size);
if (audio_buffer_size > 0) {- SDL_Log("Setting requested audio device sample frames to %d", audio_buffer_size);+ SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "Setting requested audio device sample frames to %d",
+ audio_buffer_size);
SDL_SetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES, audio_buffer_size_str);
}
@@ -131,9 +143,9 @@
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "Error opening audio output device: %s", SDL_GetError());
return 0;
}
- SDL_LogDebug(
- SDL_LOG_CATEGORY_AUDIO, "Audiospec Out: format %d, channels %d, rate %d, buffer size %d",
- audio_spec_out.format, audio_spec_out.channels, audio_spec_out.freq, audio_buffer_size_real);
+ SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO,
+ "Opening audio output: rate %dhz, buffer size: %d sample frames", audio_spec_out.freq,
+ audio_buffer_size_real);
audio_stream_in = SDL_OpenAudioDeviceStream(m8_device_id, &audio_spec_in, NULL, NULL);
if (!audio_stream_in) {@@ -155,12 +167,13 @@
return 1;
}
-void audio_close() {+void audio_close(void) {if (!audio_initialized)
return;
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.h
+++ b/src/backends/m8.h
@@ -14,7 +14,7 @@
int m8_initialize(int verbose, const char *preferred_device);
int m8_list_devices(void);
int m8_reset_display(void);
-int m8_enable_and_reset_display(void);
+int m8_enable_display(unsigned char reset_display);
int m8_send_msg_controller(unsigned char input);
int m8_send_msg_keyjazz(unsigned char note, unsigned char velocity);
int m8_process_data(const config_params_s *conf);
--- 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);
@@ -342,7 +342,7 @@
return 1;
}
-int m8_enable_and_reset_display() {+int m8_enable_display(const unsigned char reset_display) { SDL_Log("Enabling and resetting M8 display"); const char buf[1] = {'E'};@@ -352,7 +352,9 @@
return 0;
}
- result = m8_reset_display();
+ if (reset_display) {+ result = m8_reset_display();
+ }
return result;
}
--- a/src/backends/m8_libusb.c
+++ b/src/backends/m8_libusb.c
@@ -279,7 +279,7 @@
return 1;
}
-int m8_enable_and_reset_display() {+int m8_enable_display(const unsigned char reset_display) { if (devh == NULL) {return 0;
}
@@ -296,7 +296,8 @@
}
SDL_Delay(5);
- result = m8_reset_display();
+ if ()
+ result = m8_reset_display();
return result;
}
--- a/src/backends/m8_rtmidi.c
+++ b/src/backends/m8_rtmidi.c
@@ -34,7 +34,7 @@
}
static void midi_decode(const uint8_t *encoded_data, size_t length, uint8_t **decoded_data,
- size_t *decoded_length) {+ size_t *decoded_length) { if (length < m8_sysex_header_size) {// Invalid data
*decoded_data = NULL;
@@ -136,7 +136,7 @@
midi_sysex_received = false;
}
-static int initialize_rtmidi() {+static int initialize_rtmidi(void) { SDL_Log("Initializing rtmidi");midi_in = rtmidi_in_create(RTMIDI_API_UNSPECIFIED, "m8c_in", 2048);
midi_out = rtmidi_out_create(RTMIDI_API_UNSPECIFIED, "m8c_out");
@@ -171,7 +171,7 @@
return m8_midi_port_number;
}
-static int device_still_exists() {+static int device_still_exists(void) { if (midi_in == NULL || midi_out == NULL) {return 0;
};
@@ -199,7 +199,6 @@
return !result;
}
-
int m8_initialize(const int verbose, const char *preferred_device) {int m8_midi_port_number = 0;
@@ -230,11 +229,11 @@
return 1;
}
-int m8_enable_and_reset_display(void) {+int m8_enable_display(const unsigned char reset_display) {SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Sending enable command sysex");
rtmidi_in_set_callback(midi_in, midi_callback, NULL);
const unsigned char enable_sysex[8] = {0xF0, 0x00, 0x02, 0x61, 0x00, 0x00, 'E', 0xF7};- int result = rtmidi_out_send_message(midi_out, &enable_sysex[0], sizeof(enable_sysex));
+ const int result = rtmidi_out_send_message(midi_out, &enable_sysex[0], sizeof(enable_sysex));
if (result != 0) {SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Failed to send remote display enable command");
return 0;
@@ -251,8 +250,10 @@
}
SDL_Delay(5);
}
- result = m8_reset_display();
- return result;
+ if (reset_display) {+ return m8_reset_display();
+ }
+ return 1;
}
int m8_send_msg_controller(const unsigned char input) {@@ -332,13 +333,13 @@
return DEVICE_PROCESSING;
}
-int m8_close() {+int m8_close(void) {const int result = disconnect();
destroy_queue(&queue);
return result;
}
-int m8_list_devices() {+int m8_list_devices(void) { if (midi_in == NULL || midi_out == NULL) {initialize_rtmidi();
};
--- /dev/null
+++ b/src/common.h
@@ -1,0 +1,15 @@
+#ifndef COMMON_H_
+#define COMMON_H_
+#include "config.h"
+
+enum app_state { QUIT, INITIALIZE, WAIT_FOR_DEVICE, RUN };+
+struct app_context {+ config_params_s conf;
+ enum app_state app_state;
+ char *preferred_device;
+ unsigned char device_connected;
+ unsigned char app_suspended;
+ };
+
+#endif
\ No newline at end of file
--- /dev/null
+++ b/src/events.c
@@ -1,0 +1,128 @@
+#include "events.h"
+#include "backends/m8.h"
+#include "common.h"
+#include "gamepads.h"
+#include "input.h"
+#include "render.h"
+#include <SDL3/SDL.h>
+#include <SDL3/SDL_events.h>
+
+SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {+ struct app_context *ctx = appstate;
+ SDL_AppResult ret_val = SDL_APP_CONTINUE;
+
+ switch (event->type) {+
+ // --- System events ---
+ case SDL_EVENT_QUIT:
+ case SDL_EVENT_TERMINATING:
+ ret_val = SDL_APP_SUCCESS;
+ break;
+ case SDL_EVENT_DID_ENTER_BACKGROUND:
+ // 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)
+ m8_pause_processing();
+ break;
+ case SDL_EVENT_WILL_ENTER_BACKGROUND:
+ // iOS: App about to enter into the background
+ break;
+ case SDL_EVENT_WILL_ENTER_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:
+ // iOS: App becomes interactive again
+ SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Received SDL_EVENT_DID_ENTER_FOREGROUND");
+ ctx->app_suspended = 0;
+ if (ctx->device_connected) {+ renderer_clear_screen();
+ m8_resume_processing();
+ }
+ case SDL_EVENT_WINDOW_RESIZED:
+ case SDL_EVENT_WINDOW_MOVED:
+ // 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;
+
+ // --- Input events ---
+ case SDL_EVENT_GAMEPAD_ADDED:
+ case SDL_EVENT_GAMEPAD_REMOVED:
+ // Reinitialize game controllers on controller add/remove/remap
+ gamepads_initialize();
+ break;
+
+ case SDL_EVENT_KEY_DOWN:
+ input_handle_key_down_event(ctx, event);
+ break;
+
+ 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;
+ }
+ return ret_val;
+}
+
+/*
+
+// Returns the currently pressed keys to main
+input_msg_s input_get_msg(config_params_s *conf) {+
+
+
+ // Query for SDL events
+ handle_sdl_events(conf);
+
+ if (!keyjazz_enabled && keycode == (key_start | key_select | key_opt | key_edit)) {+ key = (input_msg_s){special, msg_reset_display, 0, 0};+ }
+
+ if (key.type == normal) {+ //Normal input keys go through some event-based manipulation in
+ // handle_sdl_events(), the value is stored in keycode variable
+
+ return input;
+ }
+ // Special event keys already have the correct keycode baked in
+ return key;
+}
+
+
+
+// Handles SDL input events
+static void handle_sdl_events(const config_params_s *conf) {+
+ static int prev_key_analog = 0;
+
+ SDL_Event event;
+
+ // Read joysticks
+ const int key_analog = gamecontrollers_handle_buttons(conf);
+ if (prev_key_analog != key_analog) {+ keycode = key_analog;
+ prev_key_analog = key_analog;
+ }
+
+ const input_msg_s gamepad_msg = gamecontrollers_handle_special_messages(conf);
+ if (gamepad_msg.type == special) {+ key = gamepad_msg;
+ }
+}
+*/
\ No newline at end of file
--- /dev/null
+++ b/src/events.h
@@ -1,0 +1,7 @@
+// Copyright 2021 Jonne Kokkonen
+// Released under the MIT licence, https://opensource.org/licenses/MIT
+
+#ifndef EVENTS_H_
+#define EVENTS_H_
+
+#endif
--- a/src/fx_cube.c
+++ b/src/fx_cube.c
@@ -1,7 +1,6 @@
#include "SDL2_inprint.h"
-#include <SDL3/SDL.h>
-#include <time.h>
+#include <SDL3/SDL.h>
#include <math.h>
// Handle screensaver cube effect
@@ -60,8 +59,10 @@
SDL_Texture *og_target = SDL_GetRenderTarget(fx_renderer);
- texture_size.x = (int)SDL_GetNumberProperty(SDL_GetTextureProperties(og_target),SDL_PROP_TEXTURE_WIDTH_NUMBER, 0);
- texture_size.y = (int)SDL_GetNumberProperty(SDL_GetTextureProperties(og_target),SDL_PROP_TEXTURE_HEIGHT_NUMBER, 0);
+ texture_size.x = (int)SDL_GetNumberProperty(SDL_GetTextureProperties(og_target),
+ SDL_PROP_TEXTURE_WIDTH_NUMBER, 0);
+ texture_size.y = (int)SDL_GetNumberProperty(SDL_GetTextureProperties(og_target),
+ SDL_PROP_TEXTURE_HEIGHT_NUMBER, 0);
texture_cube = SDL_CreateTexture(fx_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET,
texture_size.x, texture_size.y);
@@ -91,37 +92,51 @@
center_y = (int)(texture_size.y / 2.0);
}
-void fx_cube_destroy() {+void fx_cube_destroy(void) {+ // Free resources
SDL_DestroyTexture(texture_cube);
SDL_DestroyTexture(texture_text);
-}
-void fx_cube_update() {- SDL_FPoint points[24];
- int points_counter = 0;
- SDL_Texture *og_texture = SDL_GetRenderTarget(fx_renderer);
-
- SDL_SetRenderTarget(fx_renderer, texture_cube);
+ // Force clear renderer
+ SDL_SetRenderTarget(fx_renderer, NULL);
SDL_SetRenderDrawColor(fx_renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
SDL_RenderClear(fx_renderer);
+}
- const unsigned int seconds = SDL_GetTicks() / 1000;
- const float scale_factor = 1 + SDL_sinf((float)seconds) * (float)0.005;
+// Update the cube texture every 16ms>. Returns 1 if cube was updated, 0 if no changes were made.
+int fx_cube_update(void) {+ static Uint64 ticks_last_update = 0;
- scale(scale_factor, scale_factor, scale_factor);
- rotate_cube(M_PI / 180, M_PI / 270);
+ if (SDL_GetTicks() - ticks_last_update >= 16) {+ ticks_last_update = SDL_GetTicks();
+ SDL_FPoint points[24];
+ int points_counter = 0;
+ SDL_Texture *og_texture = SDL_GetRenderTarget(fx_renderer);
- for (int i = 0; i < 12; i++) {- const float *p1 = nodes[edges[i][0]];
- const float *p2 = nodes[edges[i][1]];
- points[points_counter++] = (SDL_FPoint){p1[0] + center_x, nodes[edges[i][0]][1] + center_y};- points[points_counter++] = (SDL_FPoint){p2[0] + center_x, p2[1] + center_y};- }
+ SDL_SetRenderTarget(fx_renderer, texture_cube);
+ SDL_SetRenderDrawColor(fx_renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
+ SDL_RenderClear(fx_renderer);
- SDL_RenderTexture(fx_renderer, texture_text, NULL, NULL);
- SDL_SetRenderDrawColor(fx_renderer, line_color.r, line_color.g, line_color.b, line_color.a);
- SDL_RenderLines(fx_renderer, points, 24);
+ const Uint64 seconds = SDL_GetTicks() / 1000;
+ const float scale_factor = 1 + SDL_sinf((float)seconds) * (float)0.005;
- SDL_SetRenderTarget(fx_renderer, og_texture);
- SDL_RenderTexture(fx_renderer, texture_cube, NULL, NULL);
+ scale(scale_factor, scale_factor, scale_factor);
+ rotate_cube(M_PI / 180, M_PI / 270);
+
+ for (int i = 0; i < 12; i++) {+ const float *p1 = nodes[edges[i][0]];
+ const float *p2 = nodes[edges[i][1]];
+ points[points_counter++] = (SDL_FPoint){p1[0] + center_x, nodes[edges[i][0]][1] + center_y};+ points[points_counter++] = (SDL_FPoint){p2[0] + center_x, p2[1] + center_y};+ }
+
+ SDL_RenderTexture(fx_renderer, texture_text, NULL, NULL);
+ SDL_SetRenderDrawColor(fx_renderer, line_color.r, line_color.g, line_color.b, line_color.a);
+ SDL_RenderLines(fx_renderer, points, 24);
+
+ SDL_SetRenderTarget(fx_renderer, og_texture);
+ SDL_RenderTexture(fx_renderer, texture_cube, NULL, NULL);
+ return 1;
+ }
+ return 0;
}
--- a/src/fx_cube.h
+++ b/src/fx_cube.h
@@ -5,6 +5,6 @@
void fx_cube_init(SDL_Renderer *target_renderer, SDL_Color foreground_color,
unsigned int texture_width, unsigned int texture_height,
unsigned int font_glyph_width);
-void fx_cube_destroy();
-void fx_cube_update();
+void fx_cube_destroy(void);
+int fx_cube_update(void);
#endif
\ No newline at end of file
--- a/src/gamecontrollers.c
+++ /dev/null
@@ -1,151 +1,0 @@
-//
-// Created by jonne on 8/19/24.
-//
-
-#include "gamecontrollers.h"
-#include "config.h"
-#include "input.h"
-
-#include <SDL3/SDL.h>
-#include <stdio.h>
-
-static int num_joysticks = 0;
-SDL_Gamepad *game_controllers[MAX_CONTROLLERS];
-
-// Opens available game controllers and returns the amount of opened controllers
-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[1024] = {0};- snprintf(db_filename, sizeof(db_filename), "%sgamecontrollerdb.txt", SDL_GetPrefPath("", "m8c"));- 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.");
- } 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);
- 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]);
- }
-}
-
-// 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, 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, 0};- }
- return msg;
-}
\ No newline at end of file
--- a/src/gamecontrollers.h
+++ /dev/null
@@ -1,18 +1,0 @@
-//
-// Created by jonne on 8/19/24.
-//
-
-#ifndef GAMECONTROLLERS_H_
-#define GAMECONTROLLERS_H_
-
-#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);
-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")) >= (int)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
@@ -1,18 +1,25 @@
+//
+// Created by Jonne Kokkonen on 15.4.2025.
+//
#include "input.h"
#include "backends/audio.h"
#include "backends/m8.h"
-#include "config.h"
-#include "gamecontrollers.h"
+#include "common.h"
#include "render.h"
#include <SDL3/SDL.h>
-uint8_t keyjazz_enabled = 0;
-uint8_t keyjazz_base_octave = 2;
-uint8_t keyjazz_velocity = 0x64;
+static unsigned char keyjazz_enabled = 0;
+static unsigned char keyjazz_base_octave = 2;
+static unsigned char keyjazz_velocity = 0x7F;
-static uint8_t keycode = 0; // value of the pressed key
+static unsigned char keycode = 0; // value of the pressed key
+static input_msg_s key = {normal, 0, 0};-static input_msg_s key = {normal, 0, 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;
@@ -21,12 +28,12 @@
}
// Get note value for a scancode, or -1 if not found
-static int get_note_for_scancode(SDL_Scancode scancode) {+static int get_note_for_scancode(const SDL_Scancode scancode) {// Map from SDL scancodes to note offsets
const struct keyjazz_scancodes_t {SDL_Scancode scancode;
- uint8_t note_offset;
+ unsigned char note_offset;
} NOTE_MAP[] = { {SDL_SCANCODE_Z, 0}, {SDL_SCANCODE_S, 1}, {SDL_SCANCODE_X, 2}, {SDL_SCANCODE_D, 3}, {SDL_SCANCODE_C, 4}, {SDL_SCANCODE_V, 5}, {SDL_SCANCODE_G, 6}, {SDL_SCANCODE_B, 7},@@ -88,12 +95,14 @@
}
}
-static input_msg_s handle_keyjazz(SDL_Event *event, uint8_t keyvalue, config_params_s *conf) {- input_msg_s key = {keyjazz, keyvalue, keyjazz_velocity, event->type};+static input_msg_s handle_keyjazz(const SDL_Event *event, unsigned char keyvalue,
+ const config_params_s *conf) {+ input_msg_s key = {keyjazz, keyvalue, keyjazz_velocity};// Check if this is a note key
const int note_value = get_note_for_scancode(event->key.scancode);
if (note_value >= 0) {+ SDL_Log("vel %d", keyjazz_velocity);key.value = note_value;
return key;
}
@@ -105,9 +114,19 @@
return key;
}
-static input_msg_s handle_normal_keys(const SDL_Event *event, const config_params_s *conf) {- // Default message with normal type and no value
- input_msg_s key = {normal, 0, 0, 0};+/**
+ * Handles key inputs based on SDL events and predefined configuration mappings.
+ * Maps SDL keyboard events to corresponding input messages.
+ * Provides a default message if no matching key mapping is found.
+ *
+ * @param event Pointer to the SDL_Event structure representing the current keyboard event.
+ * @param conf Pointer to the config_params_s structure containing key mapping configurations.
+ * @return An input_msg_s structure corresponding to the processed key input or a default message if
+ * no match is found.
+ */
+static input_msg_s handle_m8_keys(const SDL_Event *event, const config_params_s *conf) {+ // Default message with a normal type and no value
+ input_msg_s key = {normal, 0, 0};// Get the current scancode
const SDL_Scancode scancode = event->key.scancode;
@@ -115,7 +134,7 @@
// Handle standard keycodes (single key mapping)
const struct {SDL_Scancode scancode;
- uint8_t value;
+ unsigned char value;
} normal_key_map[] = { {conf->key_up, key_up}, {conf->key_left, key_left},@@ -132,15 +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},- {conf->key_toggle_audio, msg_toggle_audio},- };
-
// 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) {@@ -149,126 +159,191 @@
}
}
- // 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 SDL input events
-static void handle_sdl_events(config_params_s *conf) {+/**
+ * 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;
+ }
- static int prev_key_analog = 0;
+ 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) {+ ctx->app_state = QUIT;
+ return;
+ }
+ if (event->key.key == SDLK_ESCAPE) {+ display_keyjazz_overlay(toggle_input_keyjazz(), keyjazz_base_octave, keyjazz_velocity);
+ return;
+ }
- SDL_Event event;
+ 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;
+ }
- // Read joysticks
- const int key_analog = gamecontrollers_handle_buttons(conf);
- if (prev_key_analog != key_analog) {- keycode = key_analog;
- prev_key_analog = key_analog;
+ if (event->key.scancode == ctx->conf.key_reset && ctx->device_connected) {+ m8_reset_display();
+ return;
}
- const input_msg_s gamepad_msg = gamecontrollers_handle_special_messages(conf);
- if (gamepad_msg.type == special) {- key = gamepad_msg;
+ key = handle_m8_keys(event, &ctx->conf);
+ if (keyjazz_enabled) {+ key = handle_keyjazz(event, key.value, &ctx->conf);
}
+ keycode = (key.type == normal) ? (keycode | key.value) : key.value;
- while (SDL_PollEvent(&event)) {+ input_process_and_send(ctx);
+}
- switch (event.type) {+/**
+ * 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(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);
+ }
+ keycode = (key.type == normal) ? (keycode & ~key.value) : 0;
- // Reinitialize game controllers on controller add/remove/remap
- case SDL_EVENT_GAMEPAD_ADDED:
- case SDL_EVENT_GAMEPAD_REMOVED:
- gamecontrollers_initialize();
- break;
+ input_process_and_send(ctx);
+}
- // Handle SDL quit events (for example, window close)
- case SDL_EVENT_QUIT:
- key = (input_msg_s){special, msg_quit, 0, 0};- break;
+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;
- case SDL_EVENT_WINDOW_RESIZED:
- case SDL_EVENT_WINDOW_MOVED:
- renderer_fix_texture_scaling_after_window_resize();
- break;
+ // 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},+ };
- case SDL_EVENT_KEY_DOWN:
+ // 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 (event.key.repeat > 0) {- break;
- }
+ if (pressed && key_value != prev_key_value) {+ gamepad_state.current_buttons |= key_value;
+ } else {+ gamepad_state.current_buttons &= ~key_value;
+ }
- // ALT+ENTER toggles fullscreen
- if (event.key.key == SDLK_RETURN && (event.key.mod & SDL_KMOD_ALT) > 0) {- toggle_fullscreen();
- break;
- }
+ // Handle special button combinations
+ if (gamepad_state.current_buttons == (key_start | key_select | key_opt | key_edit)) {+ m8_reset_display();
+ return;
+ }
- // ALT+F4 quits program
- if (event.key.key == SDLK_F4 && (event.key.mod & SDL_KMOD_ALT) > 0) {- key = (input_msg_s){special, msg_quit, 0, 0};- break;
- }
+ if (pressed && button == conf->gamepad_quit && gamepad_state.current_buttons == key_select) {+ ctx->app_state = QUIT;
+ return;
+ }
- // ESC = toggle keyjazz
- if (event.key.key == SDLK_ESCAPE) {- display_keyjazz_overlay(toggle_input_keyjazz(), keyjazz_base_octave, keyjazz_velocity);
- break;
- }
+ keycode = gamepad_state.current_buttons;
- // Intentional fallthrough
- case SDL_EVENT_KEY_UP:
+ input_process_and_send(ctx);
+}
- // Normal keyboard inputs
- key = handle_normal_keys(&event, conf);
+/**
+ * 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 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) {+ 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);
+ }
+}
- if (keyjazz_enabled) {- key = handle_keyjazz(&event, key.value, conf);
- }
- break;
+/**
+ * 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;
+ gamepad_state.analog_values[axis] = value;
- default:
- break;
- }
-
- switch (key.type) {- case normal:
- if (event.type == SDL_EVENT_KEY_DOWN) {- keycode |= key.value;
- } else if (event.type == SDL_EVENT_KEY_UP) {- keycode &= ~key.value;
- }
- break;
- case keyjazz:
- // Do not allow pressing multiple keys with keyjazz
- case special:
- if (event.type == SDL_EVENT_KEY_DOWN) {- keycode = key.value;
- } else if (event.type == SDL_EVENT_KEY_UP) {- keycode = 0;
- }
- break;
- default:
- break;
- }
+ // 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);
}
+
+ keycode = gamepad_state.current_buttons;
+
+ input_process_and_send(ctx);
}
-int input_process(config_params_s *conf, enum app_state *app_state) {- static uint8_t prev_input = 0;
- static uint8_t prev_note = 0;
+/**
+ * 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;
// get current inputs
- const input_msg_s input = input_get_msg(conf);
+ const input_msg_s input = (input_msg_s){key.type, keycode, 0}; switch (input.type) {case normal:
@@ -279,57 +354,16 @@
break;
case keyjazz:
if (input.value != 0) {- if (input.eventType == SDL_EVENT_KEY_DOWN && input.value != prev_input) {- m8_send_msg_keyjazz(input.value, input.value2);
- prev_note = input.value;
- } else if (input.eventType == SDL_EVENT_KEY_UP && input.value == prev_note) {- m8_send_msg_keyjazz(0xFF, 0);
+ if (input.value != prev_input) {+ prev_input = input.value;
+ m8_send_msg_keyjazz(input.value, keyjazz_velocity);
}
+ } else {+ m8_send_msg_keyjazz(0xFF, 0);
}
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.");
- *app_state = 0;
- break;
- case msg_reset_display:
- m8_reset_display();
- break;
- case msg_toggle_audio:
- conf->audio_enabled = !conf->audio_enabled;
- audio_toggle(conf->audio_device_name, conf->audio_buffer_size);
- break;
- default:
- break;
- }
- break;
- }
+ default:;
}
return 1;
-}
-
-// Returns the currently pressed keys to main
-input_msg_s input_get_msg(config_params_s *conf) {-
- key = (input_msg_s){normal, 0, 0, 0};-
- // Query for SDL events
- handle_sdl_events(conf);
-
- if (!keyjazz_enabled && keycode == (key_start | key_select | key_opt | key_edit)) {- key = (input_msg_s){special, msg_reset_display, 0, 0};- }
-
- if (key.type == normal) {- /* Normal input keys go through some event-based manipulation in
- handle_sdl_events(), the value is stored in keycode variable */
- const input_msg_s input = (input_msg_s){key.type, keycode, 0, 0};- return input;
- }
- // Special event keys already have the correct keycode baked in
- return key;
-}
+}
\ No newline at end of file
--- a/src/input.h
+++ b/src/input.h
@@ -1,13 +1,13 @@
-// Copyright 2021 Jonne Kokkonen
-// Released under the MIT licence, https://opensource.org/licenses/MIT
+//
+// Created by Jonne Kokkonen on 15.4.2025.
+//
-#ifndef INPUT_H_
-#define INPUT_H_
-
+#ifndef INPUT_H
+#define INPUT_H
+#include "common.h"
#include "config.h"
-#include <stdint.h>
-enum app_state { QUIT, WAIT_FOR_DEVICE, RUN };+#include <SDL3/SDL_events.h>
typedef enum input_buttons_t {INPUT_UP,
@@ -36,19 +36,20 @@
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 {input_type_t type;
- uint8_t value;
- uint8_t value2;
- uint32_t eventType;
+ unsigned char value;
+ unsigned char value2;
} input_msg_s;
input_msg_s input_get_msg(config_params_s *conf);
-int input_process(config_params_s *conf, enum app_state *app_state);
+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(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);
-#endif
+#endif // INPUT_H
--- a/src/main.c
+++ b/src/main.c
@@ -6,100 +6,68 @@
// #define DEBUG_MSG
#include <SDL3/SDL.h>
+#include <SDL3/SDL_init.h>
+#define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL_main.h>
-#include <signal.h>
#include <stdlib.h>
#include "SDL2_inprint.h"
#include "backends/audio.h"
#include "backends/m8.h"
-#include "command.h"
+#include "common.h"
#include "config.h"
-#include "gamecontrollers.h"
-#include "input.h"
+#include "events.h"
+#include "gamepads.h"
#include "render.h"
-enum app_state app_state = WAIT_FOR_DEVICE;
-unsigned char device_connected = 0;
-unsigned char app_suspended = 0;
-
-// Handle CTRL+C / SIGINT, SIGKILL etc.
-static void signal_handler(int unused) {- (void)unused;
- app_state = QUIT;
-}
-
-static void initialize_signals(void) {- signal(SIGINT, signal_handler);
- signal(SIGTERM, signal_handler);
-#ifdef SIGQUIT // Not available on Windows.
- signal(SIGQUIT, signal_handler);
-#endif
-}
-
-static void do_wait_for_device(const char *preferred_device, unsigned char *m8_connected,
- config_params_s *conf) {+static void do_wait_for_device(struct app_context *ctx) {static Uint64 ticks_poll_device = 0;
- static Uint64 ticks_update_screen = 0;
+ static int screensaver_initialized = 0;
- if (*m8_connected == 0) {- screensaver_init();
+ // Handle app suspension
+ if (ctx->app_suspended) {+ return;
}
- while (app_state == WAIT_FOR_DEVICE) {+ if (!screensaver_initialized) {+ screensaver_initialized = screensaver_init();
+ }
+ screensaver_draw();
+ render_screen();
- // get current input
- const input_msg_s input = input_get_msg(&*conf);
- if (input.type == special && input.value == msg_quit) {- SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Input message QUIT.");
- app_state = QUIT;
- }
+ // Poll for M8 device every second
+ if (ctx->device_connected == 0 && SDL_GetTicks() - ticks_poll_device > 1000) {+ ticks_poll_device = SDL_GetTicks();
+ if (m8_initialize(0, ctx->preferred_device)) {-#if TARGET_OS_IOS
- // Handle app suspension
- if (app_suspended) {- SDL_Delay(conf->idle_ms);
- continue;
- }
-#endif // TARGET_OS_IOS
-
- if (SDL_GetTicks() - ticks_update_screen > 16) {- ticks_update_screen = SDL_GetTicks();
- screensaver_draw();
- render_screen();
- }
-
- // Poll for M8 device every second
- if (*m8_connected == 0 && SDL_GetTicks() - ticks_poll_device > 1000) {- ticks_poll_device = SDL_GetTicks();
- if (app_state == WAIT_FOR_DEVICE && m8_initialize(0, preferred_device) == 1) {-
- if (conf->audio_enabled == 1) {- if (audio_initialize(conf->audio_device_name, conf->audio_buffer_size) == 0) {- SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "Cannot initialize audio");
- conf->audio_enabled = 0;
- }
+ if (ctx->conf.audio_enabled) {+ if (!audio_initialize(ctx->conf.audio_device_name, ctx->conf.audio_buffer_size)) {+ SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "Cannot initialize audio");
+ ctx->conf.audio_enabled = 0;
}
+ }
- const int m8_enabled = m8_enable_and_reset_display();
- // Device was found; enable display and proceed to the main loop
- if (m8_enabled == 1) {- app_state = RUN;
- *m8_connected = 1;
- screensaver_destroy();
- } else {- SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Device not detected.");
- app_state = QUIT;
- screensaver_destroy();
+ const int m8_enabled = m8_enable_display(0);
+ // Device was found; enable display and proceed to the main loop
+ if (m8_enabled == 1) {+ ctx->app_state = RUN;
+ ctx->device_connected = 1;
+ screensaver_destroy();
+ screensaver_initialized = 0;
+ 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.");
+ ctx->app_state = QUIT;
+ screensaver_destroy();
+ screensaver_initialized = 0;
#ifdef USE_RTMIDI
- show_error_message(
- "Cannot initialize M8 remote display. Make sure you're running "
- "firmware 6.0.0 or newer. Please close and restart the application to try again.");
+ show_error_message(
+ "Cannot initialize M8 remote display. Make sure you're running "
+ "firmware 6.0.0 or newer. Please close and restart the application to try again.");
#endif
- }
}
}
- SDL_Delay(conf->idle_ms);
}
}
@@ -111,11 +79,11 @@
}
if (SDL_strcmp(argv[i], "--dev") == 0 && i + 1 < argc) {*preferred_device = argv[i + 1];
- SDL_Log("Using preferred device: %s.\n", *preferred_device);+ SDL_Log("Using preferred device: %s", *preferred_device);i++;
} else if (SDL_strcmp(argv[i], "--config") == 0 && i + 1 < argc) {*config_filename = argv[i + 1];
- SDL_Log("Using config file: %s.\n", *config_filename);+ SDL_Log("Using config file: %s", *config_filename);i++;
}
}
@@ -122,28 +90,23 @@
config_params_s conf = config_initialize(*config_filename);
#ifndef TARGET_OS_IOS
- // It's not possible to edit the config on iOS so let's just go with the defaults
+ // It's not possible to edit the config on iOS, so let's go with the defaults
config_read(&conf);
#endif
return conf;
}
-static void cleanup_resources(const unsigned char device_connected, const config_params_s *conf) {- if (conf->audio_enabled) {- audio_close();
- }
- gamecontrollers_close();
- renderer_close();
- inline_font_close();
- if (device_connected) {- m8_close();
- }
- SDL_Quit();
- SDL_Log("Shutting down.");-}
-
-static unsigned char handle_device_initialization(const unsigned char wait_for_device,
- const char *preferred_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) {SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Device not detected!");
@@ -152,131 +115,122 @@
return device_connected;
}
-#if TARGET_OS_IOS
-// IOS events handler
-static bool SDLCALL handle_app_events(void *userdata, SDL_Event *event) {- const config_params_s *conf = (config_params_s *)userdata;
- switch (event->type) {- case SDL_EVENT_TERMINATING:
- /* Terminate the app.
- Shut everything down before returning from this function.
- */
- cleanup_resources(device_connected, conf);
- return 0;
- case SDL_EVENT_DID_ENTER_BACKGROUND:
- /* This will get called if the user accepted whatever sent your app to the background.
- If the user got a phone call and canceled it, you'll instead get an
- SDL_EVENT_DID_ENTER_FOREGROUND event and restart your loops. When you get this, you have 5
- seconds to save all your state or the app will be terminated. Your app is NOT active at this
- point.
- */
- SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Received SDL_EVENT_DID_ENTER_BACKGROUND");
- app_suspended = 1;
- if (device_connected)
- m8_pause_processing();
- return 0;
- case SDL_EVENT_LOW_MEMORY:
- /* You will get this when your app is paused and iOS wants more memory.
- Release as much memory as possible.
- */
- return 0;
- case SDL_EVENT_WILL_ENTER_BACKGROUND:
- /* Prepare your app to go into the background. Stop loops, etc.
- This gets called when the user hits the home button, or gets a call.
- */
- return 0;
- case SDL_EVENT_WILL_ENTER_FOREGROUND:
- /* This call happens when your app is coming back to the foreground.
- Restore all your state here.
- */
- SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Received SDL_EVENT_WILL_ENTER_FOREGROUND");
- app_suspended = 0;
- if (device_connected)
- m8_resume_processing();
- return 0;
- case SDL_EVENT_DID_ENTER_FOREGROUND:
- /* Restart your loops here.
- Your app is interactive and getting CPU again.
- */
- SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Received SDL_EVENT_DID_ENTER_FOREGROUND");
- return 0;
- default:
- /* No special processing, add it to the event queue */
- return 1;
+// 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;
}
-}
-#endif // TARGET_OS_IOS
-static void main_loop(config_params_s *conf, const char *preferred_device) {+ struct app_context *ctx = appstate;
+ SDL_AppResult app_result = SDL_APP_CONTINUE;
- do {- if (!device_connected) {- device_connected = handle_device_initialization(conf->wait_for_device, preferred_device);
- }
- if (device_connected && m8_enable_and_reset_display()) {- if (conf->audio_enabled) {- audio_initialize(conf->audio_device_name, conf->audio_buffer_size);
- m8_reset_display(); // Avoid display glitches.
- }
- app_state = RUN;
- } else {- SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Device not detected.");
- device_connected = 0;
- app_state = conf->wait_for_device ? WAIT_FOR_DEVICE : QUIT;
- }
+ switch (ctx->app_state) {+ case INITIALIZE:
+ break;
- if (conf->wait_for_device && app_state == WAIT_FOR_DEVICE) {- do_wait_for_device(preferred_device, &device_connected, conf);
- } else if (!device_connected && app_state != WAIT_FOR_DEVICE) {- cleanup_resources(0, conf);
- exit(EXIT_FAILURE);
+ case WAIT_FOR_DEVICE:
+ if (ctx->conf.wait_for_device) {+ do_wait_for_device(ctx);
}
+ break;
- // Handle input, process data, and render screen while running.
- while (app_state == RUN) {- input_process(conf, &app_state);
- const int result = m8_process_data(conf);
- if (result == DEVICE_DISCONNECTED) {- device_connected = 0;
- app_state = WAIT_FOR_DEVICE;
- audio_close();
- } else if (result == DEVICE_FATAL_ERROR) {- app_state = QUIT;
- }
- render_screen();
- SDL_Delay(conf->idle_ms);
+ case RUN: {+ const int result = m8_process_data(&ctx->conf);
+ if (result == DEVICE_DISCONNECTED) {+ ctx->device_connected = 0;
+ ctx->app_state = WAIT_FOR_DEVICE;
+ audio_close();
+ } else if (result == DEVICE_FATAL_ERROR) {+ return SDL_APP_FAILURE;
}
- } while (app_state > QUIT);
+ render_screen();
+ break;
+ }
- cleanup_resources(device_connected, conf);
+ case QUIT:
+ app_result = SDL_APP_SUCCESS;
+ break;
+ }
+
+ return app_result;
}
-int main(const int argc, char *argv[]) {- char *preferred_device = NULL;
+// 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;
- config_params_s conf = initialize_config(argc, argv, &preferred_device, &config_filename);
- initialize_signals();
+ // Process the application's main callback roughly at 120 Hz
+ SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "120");
- device_connected = handle_device_initialization(conf.wait_for_device, preferred_device);
- if (!renderer_initialize(&conf)) {- SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Failed to initialize renderer.");
- cleanup_resources(device_connected, &conf);
- return EXIT_FAILURE;
+ 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());
+ return SDL_APP_FAILURE;
}
-#if TARGET_OS_IOS
- // IOS events handler
- SDL_SetEventFilter(handle_app_events, &conf);
-#endif
+ *appstate = ctx;
+ ctx->app_state = INITIALIZE;
- gamecontrollers_initialize();
+ ctx->conf = initialize_config(argc, argv, &ctx->preferred_device, &config_filename);
+ ctx->device_connected =
+ 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;
+ }
+
#ifndef NDEBUG
+ // 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
- main_loop(&conf, preferred_device);
- return EXIT_SUCCESS;
+ if (gamepads_initialize() < 0) {+ SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Failed to initialize game controllers.");
+ return SDL_APP_FAILURE;
+ }
+
+ 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.");
+ ctx->device_connected = 0;
+ ctx->app_state = ctx->conf.wait_for_device ? WAIT_FOR_DEVICE : QUIT;
+ }
+
+ return SDL_APP_CONTINUE;
}
+
+void SDL_AppQuit(void *appstate, SDL_AppResult result) {+ (void)result; // Suppress compiler warning
+
+ struct app_context *app = appstate;
+
+ if (app) {+ if (app->app_state == WAIT_FOR_DEVICE) {+ screensaver_destroy();
+ }
+ if (app->conf.audio_enabled) {+ audio_close();
+ }
+ gamepads_close();
+ renderer_close();
+ inline_font_close();
+ if (app->device_connected) {+ m8_close();
+ }
+ SDL_free(app);
+
+ SDL_Log("Shutting down.");+ SDL_Quit();
+ }
+}
\ No newline at end of file
--- a/src/render.c
+++ b/src/render.c
@@ -37,6 +37,8 @@
static int texture_width = 320;
static int texture_height = 240;
+static int screensaver_initialized = 0;
+
struct inline_font *fonts[5] = {&font_v1_small, &font_v1_large, &font_v2_small, &font_v2_large,&font_v2_huge};
@@ -50,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();
}
@@ -61,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,@@ -75,6 +77,8 @@
return false;
}
+ SDL_SetRenderVSync(rend, 1);
+
if (!SDL_SetRenderLogicalPresentation(rend, texture_width, texture_height, window_scaling_mode)) {SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set renderer logical presentation: %s",
SDL_GetError());
@@ -105,9 +109,7 @@
renderer_set_font_mode(0);
-#ifdef TARGET_OS_IOS
SDL_SetHint(SDL_HINT_IOS_HIDE_HOME_INDICATOR, "1");
-#endif
dirty = 1;
@@ -148,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) {@@ -180,7 +182,8 @@
SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "Font mode %i, Screen offset %i", mode, screen_offset_y);
}
-void renderer_close() {+void renderer_close(void) {+ SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "Closing renderer");
inline_font_close();
SDL_DestroyTexture(main_texture);
SDL_DestroyRenderer(rend);
@@ -187,7 +190,7 @@
SDL_DestroyWindow(win);
}
-void toggle_fullscreen() {+void toggle_fullscreen(void) {const unsigned long fullscreen_state = SDL_GetWindowFlags(win) & SDL_WINDOW_FULLSCREEN;
@@ -194,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();
@@ -211,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*/
@@ -285,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++) {@@ -331,19 +334,35 @@
dirty = 1;
}
-void render_screen() {+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++;
if (SDL_GetTicks() - ticks_fps > 5000) {@@ -354,23 +373,26 @@
}
}
-void screensaver_init() {+int screensaver_init(void) {+ if (screensaver_initialized) {+ return 1;
+ }
renderer_set_font_mode(1);
global_background_color.r = 0, global_background_color.g = 0, global_background_color.b = 0;
fx_cube_init(rend, (SDL_Color){255, 255, 255, 255}, texture_width, texture_height,fonts[font_mode]->glyph_x);
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Screensaver initialized");
+ screensaver_initialized = 1;
+ return 1;
}
-void screensaver_draw() {- fx_cube_update();
- dirty = 1;
-}
+void screensaver_draw(void) { dirty = fx_cube_update(); }-void screensaver_destroy() {+void screensaver_destroy(void) {fx_cube_destroy();
renderer_set_font_mode(0);
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Screensaver destroyed");
+ screensaver_initialized = 0;
}
void renderer_fix_texture_scaling_after_window_resize(void) {@@ -381,4 +403,14 @@
void show_error_message(const char *message) {SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "m8c error", message, win);
+}
+
+void renderer_clear_screen(void) {+ SDL_SetRenderDrawColor(rend, global_background_color.r, global_background_color.g,
+ global_background_color.b, global_background_color.a);
+ SDL_SetRenderTarget(rend, main_texture);
+ SDL_RenderClear(rend);
+ SDL_SetRenderTarget(rend, NULL);
+
+ SDL_RenderClear(rend);
}
\ No newline at end of file
--- a/src/render.h
+++ b/src/render.h
@@ -10,9 +10,10 @@
#include <stdint.h>
int renderer_initialize(config_params_s *conf);
-void renderer_close();
+void renderer_close(void);
void renderer_set_font_mode(int mode);
void renderer_fix_texture_scaling_after_window_resize(void);
+void renderer_clear_screen(void);
void draw_waveform(struct draw_oscilloscope_waveform_command *command);
void draw_rectangle(struct draw_rectangle_command *command);
@@ -20,16 +21,14 @@
void set_m8_model(unsigned int model);
-void render_screen();
-void toggle_fullscreen();
+void render_screen(void);
+void toggle_fullscreen(void);
void display_keyjazz_overlay(uint8_t show, uint8_t base_octave, uint8_t velocity);
void show_error_message(const char *message);
-void screensaver_init();
-void screensaver_draw();
-void screensaver_destroy();
-
-
+int screensaver_init(void);
+void screensaver_draw(void);
+void screensaver_destroy(void);
#endif
--
⑨