shithub: m8c

Download patch

ref: 6430599b7e26909012aa0c0aea52880394d43060
parent: d8cdc6cdff93dd09fe6c138b41877aef3edcb86c
author: Jonne Kokkonen <jonne.kokkonen@gmail.com>
date: Tue May 31 17:16:22 EDT 2022

add possibility to wait for device insertion and detect disconnetion

--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,8 @@
 #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 = main.o serial.o slip.o command.o write.o render.o ini.o config.o input.o font.o
+OBJ = main.o serial.o slip.o command.o write.o render.o ini.o config.o input.o font.o fx_cube.o
 
 #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 = serial.h slip.h command.h write.h render.h ini.h config.h input.h
+DEPS = serial.h slip.h command.h write.h render.h ini.h config.h input.h fx_cube.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 sdl2 libserialport)
--- a/config.c
+++ b/config.c
@@ -22,9 +22,11 @@
 
   c.filename = "config.ini"; // default config file to load
 
-  c.init_fullscreen = 0;  // default fullscreen state at load
-  c.init_use_gpu = 1;     // default to use hardware acceleration
-  c.idle_ms = 10;         // default to high performance
+  c.init_fullscreen = 0; // default fullscreen state at load
+  c.init_use_gpu = 1;    // default to use hardware acceleration
+  c.idle_ms = 10;        // default to high performance
+  c.wait_for_device =
+      1; // default to classic startup behaviour (exit if device disconnected)
 
   c.key_up = SDL_SCANCODE_UP;
   c.key_left = SDL_SCANCODE_LEFT;
@@ -72,7 +74,7 @@
 
   SDL_Log("Writing config file to %s", config_path);
 
-  const unsigned int INI_LINE_COUNT = 36;
+  const unsigned int INI_LINE_COUNT = 37;
 
   // Entries for the config file
   char ini_values[INI_LINE_COUNT][50];
@@ -82,6 +84,8 @@
           conf->init_fullscreen ? "true" : "false");
   sprintf(ini_values[initPointer++], "use_gpu=%s\n",
           conf->init_use_gpu ? "true" : "false");
+  sprintf(ini_values[initPointer++], "wait_for_device=%s\n",
+          conf->wait_for_device ? "true" : "false");
   sprintf(ini_values[initPointer++], "idle_ms=%d\n", conf->idle_ms);
   sprintf(ini_values[initPointer++], "[keyboard]\n");
   sprintf(ini_values[initPointer++], "key_up=%d\n", conf->key_up);
@@ -89,7 +93,8 @@
   sprintf(ini_values[initPointer++], "key_down=%d\n", conf->key_down);
   sprintf(ini_values[initPointer++], "key_right=%d\n", conf->key_right);
   sprintf(ini_values[initPointer++], "key_select=%d\n", conf->key_select);
-  sprintf(ini_values[initPointer++], "key_select_alt=%d\n", conf->key_select_alt);
+  sprintf(ini_values[initPointer++], "key_select_alt=%d\n",
+          conf->key_select_alt);
   sprintf(ini_values[initPointer++], "key_start=%d\n", conf->key_start);
   sprintf(ini_values[initPointer++], "key_start_alt=%d\n", conf->key_start_alt);
   sprintf(ini_values[initPointer++], "key_opt=%d\n", conf->key_opt);
@@ -103,7 +108,8 @@
   sprintf(ini_values[initPointer++], "gamepad_left=%d\n", conf->gamepad_left);
   sprintf(ini_values[initPointer++], "gamepad_down=%d\n", conf->gamepad_down);
   sprintf(ini_values[initPointer++], "gamepad_right=%d\n", conf->gamepad_right);
-  sprintf(ini_values[initPointer++], "gamepad_select=%d\n", conf->gamepad_select);
+  sprintf(ini_values[initPointer++], "gamepad_select=%d\n",
+          conf->gamepad_select);
   sprintf(ini_values[initPointer++], "gamepad_start=%d\n", conf->gamepad_start);
   sprintf(ini_values[initPointer++], "gamepad_opt=%d\n", conf->gamepad_opt);
   sprintf(ini_values[initPointer++], "gamepad_edit=%d\n", conf->gamepad_edit);
@@ -165,7 +171,7 @@
   // Frees the mem used for the config
   ini_free(ini);
 
-  //Write any new default options after loading
+  // Write any new default options after loading
   write_config(conf);
 }
 
@@ -173,6 +179,7 @@
   const char *param_fs = ini_get(ini, "graphics", "fullscreen");
   const char *param_gpu = ini_get(ini, "graphics", "use_gpu");
   const char *idle_ms = ini_get(ini, "graphics", "idle_ms");
+  const char *param_wait = ini_get(ini, "graphics", "wait_for_device");
 
   if (strcmpci(param_fs, "true") == 0) {
     conf->init_fullscreen = 1;
@@ -179,7 +186,7 @@
   } else
     conf->init_fullscreen = 0;
 
-  if(param_gpu != NULL){
+  if (param_gpu != NULL) {
     if (strcmpci(param_gpu, "true") == 0) {
       conf->init_use_gpu = 1;
     } else
@@ -186,9 +193,16 @@
       conf->init_use_gpu = 0;
   }
 
-  if (idle_ms)
+  if (idle_ms != NULL)
     conf->idle_ms = SDL_atoi(idle_ms);
 
+  if (param_wait != NULL) {
+    if (strcmpci(param_wait, "true") == 0) {
+      conf->wait_for_device = 1;
+    } else {
+      conf->wait_for_device = 0;
+    }
+  }
 }
 
 void read_key_config(ini_t *ini, config_params_s *conf) {
@@ -291,10 +305,17 @@
   else
     conf->gamepad_analog_invert = 0;
 
-  if (gamepad_analog_axis_updown) conf->gamepad_analog_axis_updown = SDL_atoi(gamepad_analog_axis_updown);
-  if (gamepad_analog_axis_leftright) conf->gamepad_analog_axis_leftright = SDL_atoi(gamepad_analog_axis_leftright);
-  if (gamepad_analog_axis_select) conf->gamepad_analog_axis_select = SDL_atoi(gamepad_analog_axis_select);
-  if (gamepad_analog_axis_start) conf->gamepad_analog_axis_start = SDL_atoi(gamepad_analog_axis_start);
-  if (gamepad_analog_axis_opt) conf->gamepad_analog_axis_opt = SDL_atoi(gamepad_analog_axis_opt);
-  if (gamepad_analog_axis_edit) conf->gamepad_analog_axis_edit = SDL_atoi(gamepad_analog_axis_edit);
+  if (gamepad_analog_axis_updown)
+    conf->gamepad_analog_axis_updown = SDL_atoi(gamepad_analog_axis_updown);
+  if (gamepad_analog_axis_leftright)
+    conf->gamepad_analog_axis_leftright =
+        SDL_atoi(gamepad_analog_axis_leftright);
+  if (gamepad_analog_axis_select)
+    conf->gamepad_analog_axis_select = SDL_atoi(gamepad_analog_axis_select);
+  if (gamepad_analog_axis_start)
+    conf->gamepad_analog_axis_start = SDL_atoi(gamepad_analog_axis_start);
+  if (gamepad_analog_axis_opt)
+    conf->gamepad_analog_axis_opt = SDL_atoi(gamepad_analog_axis_opt);
+  if (gamepad_analog_axis_edit)
+    conf->gamepad_analog_axis_edit = SDL_atoi(gamepad_analog_axis_edit);
 }
--- a/config.h
+++ b/config.h
@@ -11,6 +11,7 @@
   int init_fullscreen;
   int init_use_gpu;
   int idle_ms;
+  int wait_for_device;
 
   int key_up;
   int key_left;
--- a/config.ini.sample
+++ b/config.ini.sample
@@ -8,6 +8,10 @@
 fullscreen=false
 ; set this to false to run m8c in software rendering mode (may be useful for Raspberry Pi)
 use_gpu=true
+; the delay amount in ms in the main loop, decrease value for faster operation, increase value if too much cpu usage
+idle_ms = 10
+; show a spinning cube if device is not inserted
+wait_for_device = true
 
 [keyboard]
 ; these need to be the decimal value of the SDL scancodes.
--- /dev/null
+++ b/fx_cube.c
@@ -1,0 +1,137 @@
+#include <SDL.h>
+#include "SDL2_inprint.h"
+
+#define target_width 320
+#define target_height 240
+static SDL_Texture *texture_cube;
+static SDL_Texture *texture_text;
+static SDL_Texture *texture_gradient;
+static SDL_Renderer *fx_renderer;
+static SDL_Color line_color;
+
+const char *text_m8c = "M8C";
+const char *text_disconnected = "DEVICE DISCONNECTED";
+
+static const float center_x = target_width / 2;
+static const float center_y = target_height / 2;
+
+static const float default_nodes[8][3] = {
+    {-1, -1, -1}, {-1, -1, 1}, {-1, 1, -1}, {-1, 1, 1},
+    {1, -1, -1},  {1, -1, 1},  {1, 1, -1},  {1, 1, 1}};
+
+static int edges[12][2] = {{0, 1}, {1, 3}, {3, 2}, {2, 0}, {4, 5}, {5, 7},
+                           {7, 6}, {6, 4}, {0, 4}, {1, 5}, {2, 6}, {3, 7}};
+
+static float nodes[8][3];
+
+static void scale(float factor0, float factor1, float factor2) {
+  for (int i = 0; i < 8; i++) {
+    nodes[i][0] *= factor0;
+    nodes[i][1] *= factor1;
+    nodes[i][2] *= factor2;
+  }
+}
+
+static void rotate_cube(float angle_x, float angle_y) {
+  float sin_x = SDL_sin(angle_x);
+  float cos_x = SDL_cos(angle_x);
+  float sin_y = SDL_sin(angle_y);
+  float cos_y = SDL_cos(angle_y);
+  for (int i = 0; i < 8; i++) {
+    float x = nodes[i][0];
+    float y = nodes[i][1];
+    float z = nodes[i][2];
+
+    nodes[i][0] = x * cos_x - z * sin_x;
+    nodes[i][2] = z * cos_x + x * sin_x;
+
+    z = nodes[i][2];
+
+    nodes[i][1] = y * cos_y - z * sin_y;
+    nodes[i][2] = z * cos_y + y * sin_y;
+  }
+}
+
+void fx_cube_init(SDL_Renderer *target_renderer, SDL_Color foreground_color) {
+
+  fx_renderer = target_renderer;
+  line_color = foreground_color;
+
+  texture_cube =
+      SDL_CreateTexture(fx_renderer, SDL_PIXELFORMAT_ARGB8888,
+                        SDL_TEXTUREACCESS_TARGET, target_width, target_height);
+  texture_text =
+      SDL_CreateTexture(fx_renderer, SDL_PIXELFORMAT_ARGB8888,
+                        SDL_TEXTUREACCESS_TARGET, target_width, target_height);
+
+  SDL_Texture *og_target = SDL_GetRenderTarget(fx_renderer);                        
+
+  SDL_SetRenderTarget(fx_renderer, texture_text);
+
+  inprint(fx_renderer, text_disconnected, 168, 230, 0xFFFFFF, 0x000000);
+  inprint(fx_renderer, text_m8c, 2, 2, 0xFFFFFF, 0x000000);
+
+  /* Create a texture for making a linear gradient. Scaling settings make the 2
+     pixels into a gradient. This requires OpenGL or Direct3d though. */
+  SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
+  texture_gradient = SDL_CreateTexture(fx_renderer, SDL_PIXELFORMAT_ARGB8888,
+                                       SDL_TEXTUREACCESS_TARGET, 1, 2);
+  SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
+
+  SDL_SetRenderTarget(fx_renderer, texture_gradient);
+  SDL_RenderClear(fx_renderer);
+  SDL_SetRenderDrawColor(fx_renderer, 0, 0, 0, 200);
+  SDL_RenderDrawPoint(fx_renderer, 0, 1);
+  SDL_SetRenderDrawColor(fx_renderer, 0, 0, 0, SDL_ALPHA_TRANSPARENT);
+  SDL_RenderDrawPoint(fx_renderer, 0, 0);
+  SDL_SetRenderTarget(fx_renderer, og_target);
+
+  // Initialize default nodes
+  SDL_memcpy(nodes, default_nodes, sizeof(default_nodes));
+
+  scale(50, 50, 50);
+  rotate_cube(M_PI / 4, SDL_atan(SDL_sqrt(2)));
+
+  SDL_SetTextureBlendMode(texture_cube, SDL_BLENDMODE_BLEND);
+  SDL_SetTextureBlendMode(texture_gradient, SDL_BLENDMODE_BLEND);
+  SDL_SetTextureBlendMode(texture_text, SDL_BLENDMODE_BLEND);
+}
+
+void fx_cube_destroy() {
+  SDL_DestroyTexture(texture_cube);
+  SDL_DestroyTexture(texture_gradient);
+  SDL_DestroyTexture(texture_text);
+}
+
+void fx_cube_update() {
+  SDL_Point points[24];
+  int points_counter = 0;
+  SDL_Texture *og_texture = SDL_GetRenderTarget(fx_renderer);
+
+  SDL_SetRenderTarget(fx_renderer, texture_cube);
+  SDL_SetRenderDrawColor(fx_renderer, 0, 0, 0, 200);
+  SDL_RenderClear(fx_renderer);
+
+  int seconds = SDL_GetTicks() / 1000;
+  float scalefactor = 1 + (SDL_sin(seconds) * 0.01);
+
+  scale(scalefactor, scalefactor, scalefactor);
+  rotate_cube(M_PI / 180, M_PI / 270);
+
+  for (int i = 0; i < 12; i++) {
+    float *p1 = nodes[edges[i][0]];
+    float *p2 = nodes[edges[i][1]];
+    points[points_counter++] =
+        (SDL_Point){p1[0] + center_x, nodes[edges[i][0]][1] + center_y};
+    points[points_counter++] = (SDL_Point){p2[0] + center_x, p2[1] + center_y};
+  }
+
+  SDL_RenderCopy(fx_renderer, texture_gradient, NULL, NULL);
+  SDL_RenderCopy(fx_renderer, texture_text, NULL, NULL);
+  SDL_SetRenderDrawColor(fx_renderer, line_color.r, line_color.g, line_color.b,
+                         line_color.a);
+  SDL_RenderDrawLines(fx_renderer, points, 24);
+
+  SDL_SetRenderTarget(fx_renderer, og_texture);
+  SDL_RenderCopy(fx_renderer, texture_cube, NULL, NULL);
+}
--- /dev/null
+++ b/fx_cube.h
@@ -1,0 +1,8 @@
+#ifndef FX_CUBE_H_
+#define FX_CUBE_H_
+
+#include "SDL_render.h"
+void fx_cube_init(SDL_Renderer *target_renderer, SDL_Color foreground_color);
+void fx_cube_destroy();
+void fx_cube_update();
+#endif
\ No newline at end of file
--- a/input.c
+++ b/input.c
@@ -393,6 +393,7 @@
     if (event.key.keysym.sym == SDLK_F4 &&
         (event.key.keysym.mod & KMOD_ALT) > 0) {
       key = (input_msg_s){special, msg_quit};
+      break;
     }
 
     // ESC = toggle keyjazz
--- a/main.c
+++ b/main.c
@@ -1,9 +1,6 @@
 // Copyright 2021 Jonne Kokkonen
 // Released under the MIT licence, https://opensource.org/licenses/MIT
 
-// Uncomment this line to enable debug messages or call make with `make CFLAGS=-DDEBUG_MSG`
-// #define DEBUG_MSG
-
 #include <SDL.h>
 #include <libserialport.h>
 #include <signal.h>
@@ -10,6 +7,8 @@
 #include <string.h>
 #include <unistd.h>
 
+#include "SDL_log.h"
+#include "SDL_timer.h"
 #include "command.h"
 #include "config.h"
 #include "input.h"
@@ -21,12 +20,20 @@
 // maximum amount of bytes to read from the serial in one read()
 #define serial_read_size 324
 
-uint8_t run = 1;
+enum state { QUIT, WAIT_FOR_DEVICE, RUN };
+
+enum state run = WAIT_FOR_DEVICE;
 uint8_t need_display_reset = 0;
 
 // Handles CTRL+C / SIGINT
-void intHandler(int dummy) { run = 0; }
+void intHandler(int dummy) { run = QUIT; }
 
+void close_serial_port(struct sp_port *port) {
+  disconnect(port);
+  sp_close(port);
+  sp_free_port(port);
+}
+
 int main(int argc, char *argv[]) {
   // Initialize the config to defaults read in the params from the
   // configfile if present
@@ -49,109 +56,174 @@
   };
 
   static slip_handler_s slip;
+  struct sp_port *port;
 
+  uint8_t prev_input = 0;
+  uint8_t prev_note = 0;
+  uint16_t zerobyte_packets = 0; // used to detect device disconnection
+
   signal(SIGINT, intHandler);
   signal(SIGTERM, intHandler);
 
   slip_init(&slip, &slip_descriptor);
 
-  struct sp_port *port;
+  // initialize all SDL systems
+  if (initialize_sdl(conf.init_fullscreen, conf.init_use_gpu) == -1)
+    run = QUIT;
 
-  port = init_serial();
-  if (port == NULL)
-    return -1;
+/* Uncomment this line to enable debug messages or call make with `make
+   CFLAGS=-DDEBUG_MSG` #define DEBUG_MSG */
+#ifdef DEBUG_MSG
+  SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
+#endif
 
-  if (initialize_sdl(conf.init_fullscreen, conf.init_use_gpu) == -1)
-    run = 0;
+  // main loop begin
+  do {
+    port = init_serial(1);
+    if (port != NULL && enable_and_reset_display(port)) {
+      run = RUN;
+    }
 
-  if (enable_and_reset_display(port) == -1)
-    run = 0;
+    // wait until device is connected
+    if (conf.wait_for_device) {
+      static uint32_t ticks_poll_device = 0;
+      static uint32_t ticks_update_screen = 0;
 
-  #ifdef DEBUG_MSG
-  SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
-  #endif
+      if (port == NULL)
+        screensaver_init();
 
-  uint8_t prev_input = 0;
-  uint8_t prev_note = 0;
+      while (run == WAIT_FOR_DEVICE) {
+        // get current inputs
+        input_msg_s input = get_input_msg(&conf);
+        if (input.type == special && input.value == msg_quit) {
+          run = 0;
+        }
 
-  // main loop
-  while (run) {
+        if (SDL_GetTicks() - ticks_update_screen > 16) {
+          ticks_update_screen = SDL_GetTicks();
+          screensaver_draw();
+          render_screen();
+        }
 
-    // get current inputs
-    input_msg_s input = get_input_msg(&conf);
+        // Poll for M8 device every second
+        if (!port && SDL_GetTicks() - ticks_poll_device > 1000) {
+          ticks_poll_device = SDL_GetTicks();
+          port = init_serial(0);
+          if (port != NULL) {
+            // Device was found; enable display and proceed to the main loop
+            if (enable_and_reset_display(port)) {
+              run = RUN;
+              screensaver_destroy();
+            } else {
+              run = QUIT;
+            }
+          }
+        }
 
-    switch (input.type) {
-    case normal:
-      if (input.value != prev_input) {
-        prev_input = input.value;
-        send_msg_controller(port, input.value);
+        SDL_Delay(conf.idle_ms);
       }
-      break;
-    case keyjazz:
-      if (input.value != 0) {
-        if (input.eventType == SDL_KEYDOWN && input.value != prev_input) {
-          send_msg_keyjazz(port, input.value, input.value2);
-          prev_note = input.value;
-        } else if (input.eventType == SDL_KEYUP && input.value == prev_note) {
-          send_msg_keyjazz(port, 0xFF, 0);
+
+    } else {
+      // classic startup behaviour, exit if device is not found
+      if (port == NULL)
+        return -1;
+    }
+
+    // main loop
+    while (run == RUN) {
+
+      // get current inputs
+      input_msg_s input = get_input_msg(&conf);
+
+      switch (input.type) {
+      case normal:
+        if (input.value != prev_input) {
+          prev_input = input.value;
+          send_msg_controller(port, input.value);
         }
-      }
-      prev_input = input.value;
-      break;
-    case special:
-      if (input.value != prev_input) {
+        break;
+      case keyjazz:
+        if (input.value != 0) {
+          if (input.eventType == SDL_KEYDOWN && input.value != prev_input) {
+            send_msg_keyjazz(port, input.value, input.value2);
+            prev_note = input.value;
+          } else if (input.eventType == SDL_KEYUP && input.value == prev_note) {
+            send_msg_keyjazz(port, 0xFF, 0);
+          }
+        }
         prev_input = input.value;
-        switch (input.value) {
-        case msg_quit:
-          run = 0;
+        break;
+      case special:
+        if (input.value != prev_input) {
+          prev_input = input.value;
+          switch (input.value) {
+          case msg_quit:
+            run = 0;
+            break;
+          case msg_reset_display:
+            reset_display(port);
+            break;
+          default:
+            break;
+          }
           break;
-        case msg_reset_display:
-          reset_display(port);
-          break;
-        default:
-          break;
         }
-        break;
       }
-    }
 
-    while (1) {
-      // read serial port
-      int bytes_read = sp_nonblocking_read(port, serial_buf, serial_read_size);
-      if (bytes_read < 0) {
-        SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Error %d reading serial. \n",
-                        (int)bytes_read);
-        run = 0;
-        break;
-      } else if (bytes_read > 0) {
-        uint8_t *cur = serial_buf;
-        const uint8_t *end = serial_buf+bytes_read;
-        while (cur<end) {
-          // process the incoming bytes into commands and draw them
-          int n = slip_read_byte(&slip, *(cur++));
-          if (n != SLIP_NO_ERROR) {
-            if (n == SLIP_ERROR_INVALID_PACKET) {
-              reset_display(port);
+      while (1) {
+        // read serial port
+        int bytes_read =
+            sp_nonblocking_read(port, serial_buf, serial_read_size);
+        if (bytes_read < 0) {
+          SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Error %d reading serial. \n",
+                          (int)bytes_read);
+          run = QUIT;
+          break;
+        } else if (bytes_read > 0) {
+          // input from device: reset the zero byte counter and create a
+          // pointer to the serial buffer
+          zerobyte_packets = 0;
+          uint8_t *cur = serial_buf;
+          const uint8_t *end = serial_buf + bytes_read;
+          while (cur < end) {
+            // process the incoming bytes into commands and draw them
+            int n = slip_read_byte(&slip, *(cur++));
+            if (n != SLIP_NO_ERROR) {
+              if (n == SLIP_ERROR_INVALID_PACKET) {
+                reset_display(port);
+              } else {
+                SDL_LogError(SDL_LOG_CATEGORY_ERROR, "SLIP error %d\n", n);
+              }
+            }
+          }
+        } else {
+          // zero byte packet, increment counter
+          zerobyte_packets++;
+          if (zerobyte_packets > 128) {
+            // i guess it can be assumed that the device has been disconnected
+            if (conf.wait_for_device) {
+              run = WAIT_FOR_DEVICE;
+              close_serial_port(port);
+              port = NULL;
+              break;
             } else {
-              SDL_LogError(SDL_LOG_CATEGORY_ERROR, "SLIP error %d\n", n);
+              run = QUIT;
             }
           }
+          break;
         }
-      } else {
-        break;
       }
+      render_screen();
+      SDL_Delay(conf.idle_ms);
     }
-    render_screen();
-    SDL_Delay(conf.idle_ms);
-  }
+  } while (run);
+  // main loop end
 
   // exit, clean up
   SDL_Log("Shutting down\n");
   close_game_controllers();
   close_renderer();
-  disconnect(port);
-  sp_close(port);
-  sp_free_port(port);
+  close_serial_port(port);
   free(serial_buf);
   SDL_Quit();
   return 0;
--- a/render.c
+++ b/render.c
@@ -7,15 +7,17 @@
 #include <stdio.h>
 
 #include "SDL2_inprint.h"
+#include "SDL_log.h"
+#include "SDL_render.h"
 #include "command.h"
+#include "fx_cube.h"
 
 SDL_Window *win;
 SDL_Renderer *rend;
 SDL_Texture *maintexture;
 SDL_Color background_color = (SDL_Color){0, 0, 0, 0};
-static uint32_t ticks;
 
-
+static uint32_t ticks_screensaver;
 static uint32_t ticks_fps;
 static int fps;
 uint8_t fullscreen = 0;
@@ -24,7 +26,7 @@
 
 // Initializes SDL and creates a renderer and required surfaces
 int initialize_sdl(int init_fullscreen, int init_use_gpu) {
-  ticks = SDL_GetTicks();
+  //ticks = SDL_GetTicks();
 
   const int window_width = 640;  // SDL window width
   const int window_height = 480; // SDL window height
@@ -33,6 +35,8 @@
     SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "SDL_Init: %s\n", SDL_GetError());
     return -1;
   }
+  // SDL documentation recommends this
+  atexit(SDL_Quit);
 
   win = SDL_CreateWindow("m8c", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
                          window_width, window_height,
@@ -39,7 +43,8 @@
                          SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL |
                              SDL_WINDOW_RESIZABLE | init_fullscreen);
 
-  rend = SDL_CreateRenderer(win, -1, init_use_gpu ? SDL_RENDERER_ACCELERATED : SDL_RENDERER_SOFTWARE);
+  rend = SDL_CreateRenderer(
+      win, -1, init_use_gpu ? SDL_RENDERER_ACCELERATED : SDL_RENDERER_SOFTWARE);
 
   SDL_RenderSetLogicalSize(rend, 320, 240);
 
@@ -72,7 +77,8 @@
 
   int fullscreen_state = SDL_GetWindowFlags(win) & SDL_WINDOW_FULLSCREEN;
 
-  SDL_SetWindowFullscreen(win, fullscreen_state ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP);
+  SDL_SetWindowFullscreen(win,
+                          fullscreen_state ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP);
   SDL_ShowCursor(fullscreen_state);
 
   dirty = 1;
@@ -128,23 +134,25 @@
 
   static uint8_t wfm_cleared = 0;
 
-  // If the waveform is not being displayed and it's already been cleared, skip rendering it
-  if (! (wfm_cleared && command->waveform_size == 0)) {
+  // If the waveform is not being displayed and it's already been cleared, skip
+  // rendering it
+  if (!(wfm_cleared && command->waveform_size == 0)) {
 
     const SDL_Rect wf_rect = {0, 0, 320, 21};
 
     SDL_SetRenderDrawColor(rend, background_color.r, background_color.g,
-                          background_color.b, background_color.a);
+                           background_color.b, background_color.a);
     SDL_RenderFillRect(rend, &wf_rect);
 
     SDL_SetRenderDrawColor(rend, command->color.r, command->color.g,
-                          command->color.b, 255);
+                           command->color.b, 255);
 
     // Create a SDL_Point array of the waveform pixels for batch drawing
     SDL_Point waveform_points[command->waveform_size];
 
     for (int i = 0; i < command->waveform_size; i++) {
-      // Limit value because the oscilloscope commands seem to glitch occasionally
+      // Limit value because the oscilloscope commands seem to glitch
+      // occasionally
       if (command->waveform[i] > 20)
         command->waveform[i] = 20;
       waveform_points[i].x = i;
@@ -156,8 +164,7 @@
     // The packet we just drew was an empty waveform
     if (command->waveform_size == 0) {
       wfm_cleared = 1;
-    }
-    else {
+    } else {
       wfm_cleared = 0;
     }
 
@@ -165,7 +172,8 @@
   }
 }
 
-void display_keyjazz_overlay(uint8_t show, uint8_t base_octave, uint8_t velocity) {
+void display_keyjazz_overlay(uint8_t show, uint8_t base_octave,
+                             uint8_t velocity) {
 
   if (show) {
     struct draw_rectangle_command drc;
@@ -189,7 +197,7 @@
     char buf[8];
     sprintf(buf, "%02X %u", velocity, base_octave);
 
-    for (int i = 3; i >= 0; i--){
+    for (int i = 3; i >= 0; i--) {
       dcc.c = buf[i];
       draw_character(&dcc);
       dcc.pos.x -= 8;
@@ -213,7 +221,7 @@
 void render_screen() {
   if (dirty) {
     dirty = 0;
-    ticks = SDL_GetTicks();
+    //ticks = SDL_GetTicks();
     SDL_SetRenderTarget(rend, NULL);
     SDL_SetRenderDrawColor(rend, 0, 0, 0, 0);
     SDL_RenderClear(rend);
@@ -229,4 +237,19 @@
       fps = 0;
     }
   }
+}
+
+void screensaver_init() {
+  fx_cube_init(rend, (SDL_Color){255, 255, 255, 255});
+  SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Screensaver initialized");
+}
+
+void screensaver_draw() {
+  fx_cube_update();
+  dirty = 1;
+}
+
+void screensaver_destroy() {
+  fx_cube_destroy();
+  SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Screensaver destroyed");
 }
--- a/render.h
+++ b/render.h
@@ -18,4 +18,8 @@
 void toggle_fullscreen();
 void display_keyjazz_overlay(uint8_t show, uint8_t base_octave, uint8_t velocity);
 
+void screensaver_init();
+void screensaver_draw();
+void screensaver_destroy();
+
 #endif
\ No newline at end of file
--- a/serial.c
+++ b/serial.c
@@ -30,13 +30,14 @@
   return 0;
 }
 
-struct sp_port *init_serial() {
+struct sp_port *init_serial(int verbose) {
   /* A pointer to a null-terminated array of pointers to
    * struct sp_port, which will contain the ports found.*/
   struct sp_port *m8_port = NULL;
   struct sp_port **port_list;
 
-  SDL_Log("Looking for USB serial devices.\n");
+  if (verbose)
+    SDL_Log("Looking for USB serial devices.\n");
 
   /* Call sp_list_ports() to get the ports. The port_list
    * pointer will be updated to refer to the array created. */
@@ -61,29 +62,36 @@
   sp_free_port_list(port_list);
 
   if (m8_port != NULL) {
-      // Open the serial port and configure it
-      SDL_Log("Opening port.\n");
-      enum sp_return result;
+    // Open the serial port and configure it
+    SDL_Log("Opening port.\n");
+    enum sp_return result;
 
-      result = sp_open(m8_port, SP_MODE_READ_WRITE);
-      if (check(result) != SP_OK) return NULL;
-      
-      result = sp_set_baudrate(m8_port, 115200);
-      if (check(result) != SP_OK) return NULL;
-      
-      result = sp_set_bits(m8_port, 8);
-      if (check(result) != SP_OK) return NULL;
-      
-      result = sp_set_parity(m8_port, SP_PARITY_NONE);
-      if (check(result) != SP_OK) return NULL;
-      
-      result = sp_set_stopbits(m8_port, 1);
-      if (check(result) != SP_OK) return NULL;
-      
-      result = sp_set_flowcontrol(m8_port, SP_FLOWCONTROL_NONE);
-      if (check(result) != SP_OK) return NULL;
+    result = sp_open(m8_port, SP_MODE_READ_WRITE);
+    if (check(result) != SP_OK)
+      return NULL;
+
+    result = sp_set_baudrate(m8_port, 115200);
+    if (check(result) != SP_OK)
+      return NULL;
+
+    result = sp_set_bits(m8_port, 8);
+    if (check(result) != SP_OK)
+      return NULL;
+
+    result = sp_set_parity(m8_port, SP_PARITY_NONE);
+    if (check(result) != SP_OK)
+      return NULL;
+
+    result = sp_set_stopbits(m8_port, 1);
+    if (check(result) != SP_OK)
+      return NULL;
+
+    result = sp_set_flowcontrol(m8_port, SP_FLOWCONTROL_NONE);
+    if (check(result) != SP_OK)
+      return NULL;
   } else {
-    SDL_LogCritical(SDL_LOG_CATEGORY_SYSTEM, "Cannot find a M8.\n");
+    if (verbose)
+      SDL_LogCritical(SDL_LOG_CATEGORY_SYSTEM, "Cannot find a M8.\n");
   }
 
   return (m8_port);
@@ -100,7 +108,8 @@
     break;
   case SP_ERR_FAIL:
     error_message = sp_last_error_message();
-    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Failed: %s\n", error_message);
+    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Failed: %s\n",
+                 error_message);
     sp_free_error_message(error_message);
     break;
   case SP_ERR_SUPP:
@@ -107,11 +116,12 @@
     SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Not supported.\n");
     break;
   case SP_ERR_MEM:
-    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Couldn't allocate memory.\n");
+    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+                 "Error: Couldn't allocate memory.\n");
     break;
   case SP_OK:
   default:
-      break;
+    break;
   }
   return result;
 }
--- a/serial.h
+++ b/serial.h
@@ -6,6 +6,6 @@
 
 #include <libserialport.h>
 
-struct sp_port *init_serial();
+struct sp_port *init_serial(int verbose);
 
 #endif
\ No newline at end of file
--- a/write.c
+++ b/write.c
@@ -7,6 +7,19 @@
 #include <stdio.h>
 #include <unistd.h>
 
+int send_msg_dummy(struct sp_port *port){
+  SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Sending dummy packet to M8");
+  uint8_t buf[1];
+  int result;
+  buf[0] = 0x00;
+  result = sp_blocking_write(port, buf, 2, 5);
+  if (result == 1){
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
 int reset_display(struct sp_port *port){
   SDL_Log("Reset display\n");
   uint8_t buf[2];
--