shithub: m8c

Download patch

ref: 7e88427152e91ced54c4a4963b9f281e96527905
parent: 420d2f73094600290eaee2b0d9930c986ca4d04f
parent: e066ceb5c0b61c0766884f2d9d30db57e22d4bdd
author: Jonne Kokkonen <jonne.kokkonen@gmail.com>
date: Thu Jun 2 19:32:03 EDT 2022

Merge pull request #72 from laamaa/feature/kiosk

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,10 @@
 
   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 = 0; // default to exit if device disconnected
 
   c.key_up = SDL_SCANCODE_UP;
   c.key_left = SDL_SCANCODE_LEFT;
@@ -73,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;
   const unsigned int LINELEN = 50;
 
   // Entries for the config file
@@ -85,12 +86,14 @@
   snprintf(ini_values[initPointer++], LINELEN, "use_gpu=%s\n",
            conf->init_use_gpu ? "true" : "false");
   snprintf(ini_values[initPointer++], LINELEN, "idle_ms=%d\n", conf->idle_ms);
+  snprintf(ini_values[initPointer++], LINELEN, "wait_for_device=%s\n",
+           conf->wait_for_device ? "true" : "false");
   snprintf(ini_values[initPointer++], LINELEN, "[keyboard]\n");
   snprintf(ini_values[initPointer++], LINELEN, "key_up=%d\n", conf->key_up);
   snprintf(ini_values[initPointer++], LINELEN, "key_left=%d\n", conf->key_left);
   snprintf(ini_values[initPointer++], LINELEN, "key_down=%d\n", conf->key_down);
   snprintf(ini_values[initPointer++], LINELEN, "key_right=%d\n",
-          conf->key_right);
+           conf->key_right);
   snprintf(ini_values[initPointer++], LINELEN, "key_select=%d\n",
            conf->key_select);
   snprintf(ini_values[initPointer++], LINELEN, "key_select_alt=%d\n",
@@ -99,8 +102,7 @@
            conf->key_start);
   snprintf(ini_values[initPointer++], LINELEN, "key_start_alt=%d\n",
            conf->key_start_alt);
-  snprintf(ini_values[initPointer++], LINELEN, "key_opt=%d\n",
-           conf->key_opt);
+  snprintf(ini_values[initPointer++], LINELEN, "key_opt=%d\n", conf->key_opt);
   snprintf(ini_values[initPointer++], LINELEN, "key_opt_alt=%d\n",
            conf->key_opt_alt);
   snprintf(ini_values[initPointer++], LINELEN, "key_edit=%d\n", conf->key_edit);
@@ -137,14 +139,13 @@
            "gamepad_analog_axis_leftright=%d\n",
            conf->gamepad_analog_axis_leftright);
   snprintf(ini_values[initPointer++], LINELEN,
-           "gamepad_analog_axis_select=%d\n",
-           conf->gamepad_analog_axis_select);
+           "gamepad_analog_axis_select=%d\n", conf->gamepad_analog_axis_select);
   snprintf(ini_values[initPointer++], LINELEN, "gamepad_analog_axis_start=%d\n",
            conf->gamepad_analog_axis_start);
   snprintf(ini_values[initPointer++], LINELEN, "gamepad_analog_axis_opt=%d\n",
-          conf->gamepad_analog_axis_opt);
+           conf->gamepad_analog_axis_opt);
   snprintf(ini_values[initPointer++], LINELEN, "gamepad_analog_axis_edit=%d\n",
-          conf->gamepad_analog_axis_edit);
+           conf->gamepad_analog_axis_edit);
 
   // Ensure we aren't writing off the end of the array
   assert(initPointer == INI_LINE_COUNT);
@@ -188,7 +189,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);
 }
 
@@ -196,6 +197,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;
@@ -202,7 +204,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
@@ -209,9 +211,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) {
@@ -314,10 +323,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,120 @@
+#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_Renderer *fx_renderer;
+static SDL_Color line_color;
+
+const char *text_m8c = "M8C";
+const char *text_disconnected = "DEVICE DISCONNECTED";
+
+static const float center_x = (float)target_width / 2;
+static const float center_y = (float)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);
+
+  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_text, SDL_BLENDMODE_BLEND);
+}
+
+void fx_cube_destroy() {
+  SDL_DestroyTexture(texture_cube);
+  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_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
@@ -394,6 +394,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,7 +1,8 @@
 // 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`
+/* Uncomment this line to enable debug messages or call make with `make
+   CFLAGS=-DDEBUG_MSG` */
 // #define DEBUG_MSG
 
 #include <SDL.h>
@@ -21,12 +22,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 +58,196 @@
   };
 
   static slip_handler_s slip;
+  struct sp_port *port = NULL;
 
+  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;
+  // First device detection to avoid SDL init if it isn't necessary
+  if (conf.wait_for_device == 0) {
+    port = init_serial(1);
+    if (port == NULL) {
+      free(serial_buf);
+      return -1;
+    }
+  }
 
-  port = init_serial();
-  if (port == NULL)
-    return -1;
-
+  // initialize all SDL systems
   if (initialize_sdl(conf.init_fullscreen, conf.init_use_gpu) == -1)
-    run = 0;
+    run = QUIT;
 
-  if (enable_and_reset_display(port) == -1)
-    run = 0;
-
-  #ifdef DEBUG_MSG
+#ifdef DEBUG_MSG
   SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
-  #endif
+#endif
 
-  uint8_t prev_input = 0;
-  uint8_t prev_note = 0;
+  // main loop begin
+  do {
+    if (port == NULL)
+      port = init_serial(1);
+    if (port != NULL) {
+      int result;
+      result = enable_and_reset_display(port);
+      if (result == 1) {
+        run = RUN;
+      } else {
+        run = QUIT;
+      }
+    }
 
-  // main loop
-  while (run) {
+    // wait until device is connected
+    if (conf.wait_for_device) {
+      static uint32_t ticks_poll_device = 0;
+      static uint32_t ticks_update_screen = 0;
 
-    // get current inputs
-    input_msg_s input = get_input_msg(&conf);
+      if (port == NULL)
+        screensaver_init();
 
-    switch (input.type) {
-    case normal:
-      if (input.value != prev_input) {
-        prev_input = input.value;
-        send_msg_controller(port, input.value);
-      }
-      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);
+      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 = QUIT;
         }
+
+        if (SDL_GetTicks() - ticks_update_screen > 16) {
+          ticks_update_screen = SDL_GetTicks();
+          screensaver_draw();
+          render_screen();
+        }
+
+        // Poll for M8 device every second
+        if (!port && (SDL_GetTicks() - ticks_poll_device > 1000)) {
+          ticks_poll_device = SDL_GetTicks();
+          port = init_serial(0);
+          if (run == WAIT_FOR_DEVICE && port != NULL) {
+            int result = enable_and_reset_display(port);
+            SDL_Delay(100);
+            // Device was found; enable display and proceed to the main loop
+            if (result == 1) {
+              run = RUN;
+              screensaver_destroy();
+            } else {
+              run = QUIT;
+              screensaver_destroy();
+            }
+          }
+        }
+
+        SDL_Delay(conf.idle_ms);
       }
-      prev_input = input.value;
-      break;
-    case special:
-      if (input.value != prev_input) {
+
+    } else {
+      // classic startup behaviour, exit if device is not found
+      if (port == NULL) {
+        close_game_controllers();
+        close_renderer();
+        SDL_Quit();
+        free(serial_buf);
+        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);
+        }
+        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 > QUIT);
+  // 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,16 @@
 #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_fps;
 static int fps;
 uint8_t fullscreen = 0;
@@ -24,7 +25,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 +34,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 +42,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 +76,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 +133,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 +163,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 +171,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 +196,7 @@
     char buf[8];
     snprintf(buf, sizeof(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 +220,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 +236,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
@@ -1,6 +1,7 @@
 // Copyright 2021 Jonne Kokkonen
 // Released under the MIT licence, https://opensource.org/licenses/MIT
 
+#include "SDL_timer.h"
 #include <SDL_log.h>
 #include <libserialport.h>
 #include <stdint.h>
@@ -7,7 +8,7 @@
 #include <stdio.h>
 #include <unistd.h>
 
-int reset_display(struct sp_port *port){
+int reset_display(struct sp_port *port) {
   SDL_Log("Reset display\n");
   uint8_t buf[2];
   int result;
@@ -14,10 +15,11 @@
 
   buf[0] = 0x45;
   buf[1] = 0x52;
-    
+
   result = sp_blocking_write(port, buf, 2, 5);
   if (result != 2) {
-    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error resetting M8 display, code %d", result);
+    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error resetting M8 display, code %d",
+                 result);
     return 0;
   }
   return 1;
@@ -32,15 +34,17 @@
   buf[0] = 0x44;
   result = sp_blocking_write(port, buf, 1, 5);
   if (result != 1) {
-    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error enabling M8 display, code %d", result);
+    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error enabling M8 display, code %d",
+                 result);
     return 0;
   }
 
-  usleep(500);
-  if (!reset_display(port)){
+  SDL_Delay(5);
+  result = reset_display(port);
+  if (result == 1)
+    return 1;
+  else
     return 0;
-  }
-  return 1;
 }
 
 int disconnect(struct sp_port *port) {
@@ -51,7 +55,8 @@
 
   result = sp_blocking_write(port, buf, 1, 5);
   if (result != 1) {
-    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error sending disconnect, code %d", result);
+    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error sending disconnect, code %d",
+                 result);
     return -1;
   }
   return 1;
@@ -58,27 +63,28 @@
 }
 
 int send_msg_controller(struct sp_port *port, uint8_t input) {
-  char buf[2] = {'C',input};
+  char buf[2] = {'C', input};
   size_t nbytes = 2;
   int result;
   result = sp_blocking_write(port, buf, nbytes, 5);
   if (result != nbytes) {
-    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error sending input, code %d", result);
+    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error sending input, code %d",
+                 result);
     return -1;
   }
   return 1;
-
 }
 
 int send_msg_keyjazz(struct sp_port *port, uint8_t note, uint8_t velocity) {
   if (velocity > 0x7F)
     velocity = 0x7F;
-  char buf[3] = {'K',note,velocity};
+  char buf[3] = {'K', note, velocity};
   size_t nbytes = 3;
   int result;
-  result = sp_blocking_write(port, buf, nbytes,5);
+  result = sp_blocking_write(port, buf, nbytes, 5);
   if (result != nbytes) {
-    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error sending keyjazz, code %d", result);
+    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error sending keyjazz, code %d",
+                 result);
     return -1;
   }
 
--