diff --git a/meson.build b/meson.build
index 876eca31..2972adc0 100644
--- a/meson.build
+++ b/meson.build
@@ -30,6 +30,7 @@ openrtx_src = ['openrtx/src/state.c',
'openrtx/src/input.c',
'openrtx/src/calibUtils.c',
'openrtx/src/queue.c',
+ 'openrtx/src/chan.c',
'openrtx/src/rtx/rtx.cpp',
'openrtx/src/rtx/OpMode_FM.cpp',
'openrtx/src/rtx/OpMode_M17.cpp',
@@ -214,6 +215,7 @@ mk22fn512_def = {'_POSIX_PRIORITY_SCHEDULING':''}
## Linux
##
linux_platform_src = ['platform/targets/linux/emulator/emulator.c',
+ 'platform/targets/linux/emulator/sdl_engine.c',
'platform/drivers/display/display_libSDL.c',
'platform/drivers/keyboard/keyboard_linux.c',
'platform/drivers/NVM/nvmem_linux.c',
diff --git a/openrtx/include/chan.h b/openrtx/include/chan.h
new file mode 100644
index 00000000..54d78968
--- /dev/null
+++ b/openrtx/include/chan.h
@@ -0,0 +1,50 @@
+/***************************************************************************
+ * Copyright (C) 2021 by Alessio Caiazza IU5BON *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, see *
+ ***************************************************************************/
+#ifndef CHAN_H
+#define CHAN_H
+
+#include
+#include
+
+/*
+ * chan_t is an unbuffered synchronization channel.
+ * Both reader and writer are blocked untill the data is exchanged.
+ */
+typedef struct chan_t
+{
+ pthread_mutex_t m_meta;
+ pthread_mutex_t m_read;
+ pthread_mutex_t m_write;
+ pthread_cond_t c_reader;
+ pthread_cond_t c_writer;
+
+ void *data;
+ bool closed;
+ bool reader;
+ bool writer;
+}
+chan_t;
+
+void chan_init(chan_t *c);
+void chan_send(chan_t *c, void *data);
+void chan_recv(chan_t *c, void **data);
+bool chan_can_recv(chan_t *c);
+bool chan_can_send(chan_t *c);
+void chan_close(chan_t *c);
+void chan_terminate(chan_t *c);
+
+#endif
diff --git a/openrtx/src/chan.c b/openrtx/src/chan.c
new file mode 100644
index 00000000..0c3e21c3
--- /dev/null
+++ b/openrtx/src/chan.c
@@ -0,0 +1,140 @@
+/***************************************************************************
+ * Copyright (C) 2021 by Alessio Caiazza IU5BON *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, see *
+ ***************************************************************************/
+#include "chan.h"
+#include
+
+void chan_init(chan_t *c)
+{
+ if(c == NULL) return;
+
+ pthread_mutex_init(&c->m_meta, NULL);
+ pthread_mutex_init(&c->m_read, NULL);
+ pthread_mutex_init(&c->m_write, NULL);
+
+ pthread_cond_init(&c->c_reader, NULL);
+ pthread_cond_init(&c->c_writer, NULL);
+
+ c->reader = false;
+ c->writer = false;
+ c->closed = false;
+}
+
+void chan_send(chan_t *c, void *data)
+{
+ pthread_mutex_lock(&c->m_write);
+ pthread_mutex_lock(&c->m_meta);
+
+ if(c->closed)
+ {
+ pthread_mutex_unlock(&c->m_meta);
+ pthread_mutex_unlock(&c->m_write);
+
+ return;
+ }
+
+ c->data = data;
+ c->writer = true;
+
+ // notify the waiting reader that data is ready
+ if (c->reader)
+ {
+ pthread_cond_signal(&c->c_writer);
+ }
+
+ // wait until data is consumed
+ pthread_cond_wait(&c->c_reader, &c->m_meta);
+ c->writer = false;
+
+ pthread_mutex_unlock(&c->m_meta);
+ pthread_mutex_unlock(&c->m_write);
+}
+
+void chan_recv(chan_t *c, void **data)
+{
+ pthread_mutex_lock(&c->m_read);
+ pthread_mutex_lock(&c->m_meta);
+
+ // wait for a writer
+ while(!c->closed && !c->writer)
+ {
+ c->reader = true;
+ pthread_cond_wait(&c->c_writer, &c->m_meta);
+ c->reader = false;
+ }
+
+ if(c->closed)
+ {
+ pthread_mutex_unlock(&c->m_meta);
+ pthread_mutex_unlock(&c->m_read);
+
+ return;
+ }
+
+ if (data != NULL)
+ {
+ *data = c->data;
+ }
+
+ // notify the waiting writer that the reader consumed the data
+ pthread_cond_signal(&c->c_reader);
+
+ pthread_mutex_unlock(&c->m_meta);
+ pthread_mutex_unlock(&c->m_read);
+}
+
+
+bool chan_can_recv(chan_t *c)
+{
+ pthread_mutex_lock(&c->m_meta);
+ bool can_receive = c->writer;
+ pthread_mutex_unlock(&c->m_meta);
+
+ return can_receive;
+}
+
+bool chan_can_send(chan_t *c)
+{
+ pthread_mutex_lock(&c->m_meta);
+ bool can_send = c->reader;
+ pthread_mutex_unlock(&c->m_meta);
+
+ return can_send;
+}
+
+void chan_close(chan_t *c)
+{
+ pthread_mutex_lock(&c->m_meta);
+ if (!c->closed)
+ {
+ c->closed = true;
+ pthread_cond_broadcast(&c->c_reader);
+ pthread_cond_broadcast(&c->c_writer);
+ }
+ pthread_mutex_unlock(&c->m_meta);
+}
+
+void chan_terminate(chan_t *c)
+{
+ chan_close(c);
+
+ pthread_mutex_destroy(&c->m_write);
+ pthread_mutex_destroy(&c->m_read);
+ pthread_mutex_destroy(&c->m_meta);
+
+ pthread_cond_destroy(&c->c_writer);
+ pthread_cond_destroy(&c->c_reader);
+}
diff --git a/openrtx/src/main.c b/openrtx/src/main.c
index 470026de..aff3ca73 100644
--- a/openrtx/src/main.c
+++ b/openrtx/src/main.c
@@ -27,6 +27,9 @@
#include
#include
#include
+#ifdef PLATFORM_LINUX
+#include
+#endif
extern void *ui_task(void *arg);
@@ -74,6 +77,16 @@ int main(void)
// Create OpenRTX threads
create_threads();
+#ifndef PLATFORM_LINUX
// Jump to the UI task
ui_task(NULL);
+#else
+ // macOS requires SDL main loop to run on the main thread.
+ // Here we create a new thread for ui_task and utilize the masin thread for
+ // the SDL main loop.
+ pthread_t ui_thread;
+ pthread_create(&ui_thread, NULL, ui_task, NULL);
+
+ sdl_task();
+#endif
}
diff --git a/openrtx/src/threads.c b/openrtx/src/threads.c
index 0eeaa9ba..8e9a27a3 100644
--- a/openrtx/src/threads.c
+++ b/openrtx/src/threads.c
@@ -38,10 +38,6 @@
#include
#include
#endif
-#ifdef PLATFORM_LINUX
-#include
-#endif
-
/* Mutex for concurrent access to state variable */
pthread_mutex_t state_mutex;
@@ -77,10 +73,6 @@ void *ui_task(void *arg)
while(1)
{
- #ifdef PLATFORM_LINUX
- emulator_process_sdl_events();
- #endif
-
// Read from the keyboard queue (returns 0 if no message is present)
// Copy keyboard_t keys from received void * pointer msg
event_t event;
diff --git a/platform/drivers/display/display_libSDL.c b/platform/drivers/display/display_libSDL.c
index 95837ce2..4c0653e3 100644
--- a/platform/drivers/display/display_libSDL.c
+++ b/platform/drivers/display/display_libSDL.c
@@ -25,139 +25,20 @@
*/
#include
+#include
+#include
#include
#include
#include
-#undef main /* necessary to avoid conflicts with SDL_main */
-
-/*
- * Screen dimensions, adjust basing on the size of the screen you need to
- * emulate
- */
-#ifndef SCREEN_WIDTH
-#define SCREEN_WIDTH 160
-#endif
-
-#ifndef SCREEN_HEIGHT
-#define SCREEN_HEIGHT 128
-#endif
-
-#ifdef PIX_FMT_RGB565
-#define PIXEL_FORMAT SDL_PIXELFORMAT_RGB565
-#define PIXEL_SIZE uint16_t
-#else
-#define PIXEL_FORMAT SDL_PIXELFORMAT_ARGB8888
-#define PIXEL_SIZE uint32_t
-#endif
-
-SDL_Renderer *renderer; /* SDL renderer */
-SDL_Window *window; /* SDL window */
-SDL_Texture *displayTexture; /* SDL rendering surface */
void *frameBuffer; /* Pointer to framebuffer */
bool inProgress; /* Flag to signal when rendering is in progress */
-
-int screenshot_display(const char *filename)
-{
- //https://stackoverflow.com/a/48176678
- //user1902824
- //modified to keep renderer and display texture references in the body rather than as a parameter
- SDL_Renderer * ren = renderer;
- SDL_Texture * tex = displayTexture;
- int err = 0;
-
-
- SDL_Texture *ren_tex;
- SDL_Surface *surf;
- int st;
- int w;
- int h;
- int format;
- void *pixels;
-
- pixels = NULL;
- surf = NULL;
- ren_tex = NULL;
- format = SDL_PIXELFORMAT_RGBA32;
-
- /* Get information about texture we want to save */
- st = SDL_QueryTexture(tex, NULL, NULL, &w, &h);
- if (st != 0)
- {
- SDL_Log("Failed querying texture: %s\n", SDL_GetError());
- err++;
- goto cleanup;
- }
-
- ren_tex = SDL_CreateTexture(ren, format, SDL_TEXTUREACCESS_TARGET, w, h);
- if (!ren_tex)
- {
- SDL_Log("Failed creating render texture: %s\n", SDL_GetError());
- err++;
- goto cleanup;
- }
-
- /*
- * Initialize our canvas, then copy texture to a target whose pixel data we
- * can access
- */
- st = SDL_SetRenderTarget(ren, ren_tex);
- if (st != 0)
- {
- SDL_Log("Failed setting render target: %s\n", SDL_GetError());
- err++;
- goto cleanup;
- }
- SDL_SetRenderDrawColor(ren, 0x00, 0x00, 0x00, 0x00);
- SDL_RenderClear(ren);
- st = SDL_RenderCopy(ren, tex, NULL, NULL);
- if (st != 0)
- {
- SDL_Log("Failed copying texture data: %s\n", SDL_GetError());
- err++;
- goto cleanup;
- }
- /* Create buffer to hold texture data and load it */
- pixels = malloc(w * h * SDL_BYTESPERPIXEL(format));
- if (!pixels)
- {
- SDL_Log("Failed allocating memory\n");
- err++;
- goto cleanup;
- }
- st = SDL_RenderReadPixels(ren, NULL, format, pixels, w * SDL_BYTESPERPIXEL(format));
- if (st != 0)
- {
- SDL_Log("Failed reading pixel data: %s\n", SDL_GetError());
- err++;
- goto cleanup;
- }
- /* Copy pixel data over to surface */
- surf = SDL_CreateRGBSurfaceWithFormatFrom(pixels, w, h, SDL_BITSPERPIXEL(format), w * SDL_BYTESPERPIXEL(format), format);
- if (!surf)
- {
- SDL_Log("Failed creating new surface: %s\n", SDL_GetError());
- err++;
- goto cleanup;
- }
- /* Save result to an image */
- st = SDL_SaveBMP(surf, filename);
- if (st != 0)
- {
- SDL_Log("Failed saving image: %s\n", SDL_GetError());
- err++;
- goto cleanup;
- }
- SDL_Log("Saved texture as BMP to \"%s\"\n", filename);
-
-cleanup:
- SDL_FreeSurface(surf);
- free(pixels);
- SDL_DestroyTexture(ren_tex);
- return err;
-}
-
+/*
+ * SDL main loop syncronization
+ */
+bool sdl_ready = false; /* Flag to signal the sdl main loop is running */
+extern chan_t fb_sync; /* Shared channel to send a frame buffer update */
/**
* @internal
@@ -197,60 +78,36 @@ uint32_t fetchPixelFromFb(unsigned int x, unsigned int y)
void display_init()
{
- if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0)
- {
- printf("SDL video init error!!\n");
-
- }
- else
- {
-
- window = SDL_CreateWindow("OpenRTX",
- SDL_WINDOWPOS_UNDEFINED,
- SDL_WINDOWPOS_UNDEFINED,
- SCREEN_WIDTH * 3, SCREEN_HEIGHT * 3,
- SDL_WINDOW_SHOWN );
- //removed RESIZABLE flag so automatic screen recording is a little easier
-
- renderer = SDL_CreateRenderer(window, -1, 0);
- SDL_RenderSetLogicalSize(renderer, SCREEN_WIDTH, SCREEN_HEIGHT);
- displayTexture = SDL_CreateTexture(renderer, PIXEL_FORMAT, SDL_TEXTUREACCESS_STREAMING,
- SCREEN_WIDTH, SCREEN_HEIGHT);
- SDL_RenderClear(renderer);
- SDL_RenderCopy(renderer, displayTexture, NULL, NULL);
- SDL_RenderPresent(renderer);
-
- /*
- * Black and white pixel format: framebuffer type is uint8_t where each
- * bit represents a pixel. We have to allocate
- * (SCREEN_HEIGHT * SCREEN_WIDTH)/8 elements
- */
+ /*
+ * Black and white pixel format: framebuffer type is uint8_t where each
+ * bit represents a pixel. We have to allocate
+ * (SCREEN_HEIGHT * SCREEN_WIDTH)/8 elements
+ */
#ifdef PIX_FMT_BW
- unsigned int fbSize = (SCREEN_HEIGHT * SCREEN_WIDTH)/8;
- if((fbSize * 8) < (SCREEN_HEIGHT * SCREEN_WIDTH)) fbSize += 1; /* Compensate for eventual truncation error in division */
- fbSize *= sizeof(uint8_t);
+ unsigned int fbSize = (SCREEN_HEIGHT * SCREEN_WIDTH)/8;
+ if((fbSize * 8) < (SCREEN_HEIGHT * SCREEN_WIDTH)) fbSize += 1; /* Compensate for eventual truncation error in division */
+ fbSize *= sizeof(uint8_t);
#endif
- /*
- * Grayscale pixel format: framebuffer type is uint8_t where each element
- * controls one pixel
- */
+ /*
+ * Grayscale pixel format: framebuffer type is uint8_t where each element
+ * controls one pixel
+ */
#ifdef PIX_FMT_GRAYSC
- unsigned int fbSize = SCREEN_HEIGHT * SCREEN_WIDTH * sizeof(uint8_t);
+ unsigned int fbSize = SCREEN_HEIGHT * SCREEN_WIDTH * sizeof(uint8_t);
#endif
- /*
- * RGB565 pixel format: framebuffer type is uint16_t where each element
- * controls one pixel
- */
+ /*
+ * RGB565 pixel format: framebuffer type is uint16_t where each element
+ * controls one pixel
+ */
#ifdef PIX_FMT_RGB565
- unsigned int fbSize = SCREEN_HEIGHT * SCREEN_WIDTH * sizeof(uint16_t);
+ unsigned int fbSize = SCREEN_HEIGHT * SCREEN_WIDTH * sizeof(uint16_t);
#endif
- frameBuffer = malloc(fbSize);
- memset(frameBuffer, 0xFFFF, fbSize);
- inProgress = false;
- }
+ frameBuffer = malloc(fbSize);
+ memset(frameBuffer, 0xFFFF, fbSize);
+ inProgress = false;
}
void display_terminate()
@@ -259,36 +116,36 @@ void display_terminate()
{} /* Wait until current render finishes */
printf("Terminating SDL display emulator, goodbye!\n");
free(frameBuffer);
- SDL_DestroyWindow(window);
- SDL_Quit();
}
void display_renderRows(uint8_t startRow, uint8_t endRow)
{
(void) startRow;
(void) endRow;
- PIXEL_SIZE *pixels;
- int pitch = 0;
- if (SDL_LockTexture(displayTexture, NULL, (void **) &pixels, &pitch) < 0)
- {
- printf("SDL_lock failed: %s\n", SDL_GetError());
- }
inProgress = true;
-#ifdef PIX_FMT_RGB565
- uint16_t *fb = (uint16_t *) (frameBuffer);
- memcpy(pixels, fb, sizeof(uint16_t) * SCREEN_HEIGHT * SCREEN_WIDTH);
-#else
- for (unsigned int x = 0; x < SCREEN_WIDTH; x++)
+ if(!sdl_ready)
{
- for (unsigned int y = startRow; y < endRow; y++)
- {
- pixels[x + y * SCREEN_WIDTH] = fetchPixelFromFb(x, y);
- }
+ sdl_ready = sdl_main_loop_ready();
}
+
+ if(sdl_ready)
+ {
+#ifdef PIX_FMT_RGB565
+ chan_send(&fb_sync, frameBuffer);
+#else
+ //TODO free
+ PIXEL_SIZE *pixels = malloc(sizeof(uint16_t) * SCREEN_HEIGHT * SCREEN_WIDTH);
+ for (unsigned int x = 0; x < SCREEN_WIDTH; x++)
+ {
+ for (unsigned int y = startRow; y < endRow; y++)
+ {
+ pixels[x + y * SCREEN_WIDTH] = fetchPixelFromFb(x, y);
+ }
+ }
+ chan_send(&fb_sync, (void *)pixels);
#endif
- SDL_UnlockTexture(displayTexture);
- SDL_RenderCopy(renderer, displayTexture, NULL, NULL);
- SDL_RenderPresent(renderer);
+ }
+
inProgress = false;
}
@@ -311,4 +168,3 @@ void display_setContrast(uint8_t contrast)
{
printf("Setting display contrast to %d\n", contrast);
}
-
diff --git a/platform/targets/linux/emulator/emulator.c b/platform/targets/linux/emulator/emulator.c
index 4d5f64d3..55a76f14 100644
--- a/platform/targets/linux/emulator/emulator.c
+++ b/platform/targets/linux/emulator/emulator.c
@@ -20,22 +20,24 @@
#include "emulator.h"
+#include "sdl_engine.h"
#include
#include
#include
#include
+#include
#include
#include
#include
#include
+/* Custom SDL Event to request a screenshot */
+extern Uint32 SDL_Screenshot_Event;
radio_state Radio_State = {12, 8.2f, 3, 4, 1, false, false};
-extern int screenshot_display(const char *filename);
-
typedef int (*_climenu_fn)(void *self, int argc, char **argv);
typedef struct
@@ -250,8 +252,14 @@ int screenshot(void *_self, int _argc, char **_argv)
{
filename = _argv[0];
}
- return screenshot_display(filename) == 0 ? SH_CONTINUE : SH_ERR;
- //screenshot_display returns 0 if ok, which is same as SH_CONTINUE
+
+ SDL_Event e;
+ SDL_zero(e);
+ e.type = SDL_Screenshot_Event;
+ e.user.data1 = malloc(sizeof(filename));
+ strcpy(e.user.data1, filename);
+
+ return SDL_PushEvent(&e) == 1 ? SH_CONTINUE : SH_ERR;
}
/*
int record_start(void * _self, int _argc, char ** _argv ){
@@ -528,116 +536,15 @@ void *startCLIMenu()
Radio_State.PowerOff = true;
}
-
void emulator_start()
{
+ init_sdl();
+
pthread_t cli_thread;
int err = pthread_create(&cli_thread, NULL, startCLIMenu, NULL);
if(err)
{
- printf("An error occurred starting the emulator thread: %d\n", err);
- }
-}
-
-keyboard_t sdl_keys;
-keyboard_t sdl_getKeys() { return sdl_keys; }
-
-bool sdk_key_code_to_key(SDL_KeyCode sym, keyboard_t *key)
-{
- switch (sym) {
- case SDLK_0:
- *key = KEY_0;
- return true;
- case SDLK_1:
- *key = KEY_1;
- return true;
- case SDLK_2:
- *key = KEY_2;
- return true;
- case SDLK_3:
- *key = KEY_3;
- return true;
- case SDLK_4:
- *key = KEY_4;
- return true;
- case SDLK_5:
- *key = KEY_5;
- return true;
- case SDLK_6:
- *key = KEY_6;
- return true;
- case SDLK_7:
- *key = KEY_7;
- return true;
- case SDLK_8:
- *key = KEY_8;
- return true;
- case SDLK_9:
- *key = KEY_9;
- return true;
- case SDLK_ASTERISK:
- *key = KEY_STAR;
- return true;
- case SDLK_ESCAPE:
- *key = KEY_ESC;
- return true;
- case SDLK_LEFT:
- *key = KEY_LEFT;
- return true;
- case SDLK_RIGHT:
- *key = KEY_RIGHT;
- return true;
- case SDLK_RETURN:
- *key = KEY_ENTER;
- return true;
- case SDLK_HASH:
- *key = KEY_HASH;
- return true;
- case SDLK_n:
- *key = KEY_F1;
- return true;
- case SDLK_m:
- *key = KEY_MONI;
- return true;
- case SDLK_PAGEUP:
- *key = KNOB_LEFT;
- return true;
- case SDLK_PAGEDOWN:
- *key = KNOB_RIGHT;
- return true;
- case SDLK_UP:
- *key = KEY_UP;
- return true;
- case SDLK_DOWN:
- *key = KEY_DOWN;
- return true;
- default:
- return false;
- }
-}
-
-void emulator_process_sdl_events()
-{
- Uint32 now = SDL_GetTicks();
-
- SDL_Event ev = { 0 };
- keyboard_t key = 0;
-
- SDL_PollEvent( &ev);
-
- switch (ev.type) {
- case SDL_QUIT:
- Radio_State.PowerOff = true;
- break;
- case SDL_KEYDOWN:
- if (sdk_key_code_to_key(ev.key.keysym.sym, &key))
- sdl_keys |= key;
- break;
- case SDL_KEYUP:
- if (sdk_key_code_to_key(ev.key.keysym.sym, &key))
- sdl_keys ^= key;
-
- break;
+ printf("An error occurred starting the emulator CLI thread: %d\n", err);
}
}
diff --git a/platform/targets/linux/emulator/sdl_engine.c b/platform/targets/linux/emulator/sdl_engine.c
new file mode 100644
index 00000000..26d20efd
--- /dev/null
+++ b/platform/targets/linux/emulator/sdl_engine.c
@@ -0,0 +1,335 @@
+/***************************************************************************
+ * Copyright (C) 2021 by Alessio Caiazza IU5BON *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, see *
+ ***************************************************************************/
+#include "sdl_engine.h"
+#include "emulator.h"
+
+#include
+#include
+#include
+#include
+
+int screenshot_display(const char *filename);
+
+/* Shared channel to receive frame buffer updates */
+chan_t fb_sync;
+
+SDL_Window *window;
+SDL_Renderer *renderer;
+SDL_Texture *displayTexture;
+
+/* Custom SDL Event to request a screenshot */
+Uint32 SDL_Screenshot_Event;
+
+/*
+ * Mutex protected variables
+ */
+pthread_mutex_t mu;
+bool ready = false; /* Signal if the main loop is ready */
+keyboard_t sdl_keys; /* Store the keyboard status */
+
+bool sdk_key_code_to_key(SDL_KeyCode sym, keyboard_t *key)
+{
+ switch (sym) {
+ case SDLK_0:
+ *key = KEY_0;
+ return true;
+ case SDLK_1:
+ *key = KEY_1;
+ return true;
+ case SDLK_2:
+ *key = KEY_2;
+ return true;
+ case SDLK_3:
+ *key = KEY_3;
+ return true;
+ case SDLK_4:
+ *key = KEY_4;
+ return true;
+ case SDLK_5:
+ *key = KEY_5;
+ return true;
+ case SDLK_6:
+ *key = KEY_6;
+ return true;
+ case SDLK_7:
+ *key = KEY_7;
+ return true;
+ case SDLK_8:
+ *key = KEY_8;
+ return true;
+ case SDLK_9:
+ *key = KEY_9;
+ return true;
+ case SDLK_ASTERISK:
+ *key = KEY_STAR;
+ return true;
+ case SDLK_ESCAPE:
+ *key = KEY_ESC;
+ return true;
+ case SDLK_LEFT:
+ *key = KEY_LEFT;
+ return true;
+ case SDLK_RIGHT:
+ *key = KEY_RIGHT;
+ return true;
+ case SDLK_RETURN:
+ *key = KEY_ENTER;
+ return true;
+ case SDLK_HASH:
+ *key = KEY_HASH;
+ return true;
+ case SDLK_n:
+ *key = KEY_F1;
+ return true;
+ case SDLK_m:
+ *key = KEY_MONI;
+ return true;
+ case SDLK_PAGEUP:
+ *key = KNOB_LEFT;
+ return true;
+ case SDLK_PAGEDOWN:
+ *key = KNOB_RIGHT;
+ return true;
+ case SDLK_UP:
+ *key = KEY_UP;
+ return true;
+ case SDLK_DOWN:
+ *key = KEY_DOWN;
+ return true;
+ default:
+ return false;
+ }
+}
+
+int screenshot_display(const char *filename)
+{
+ //https://stackoverflow.com/a/48176678
+ //user1902824
+ //modified to keep renderer and display texture references in the body rather than as a parameter
+ SDL_Renderer * ren = renderer;
+ SDL_Texture * tex = displayTexture;
+ int err = 0;
+
+
+ SDL_Texture *ren_tex;
+ SDL_Surface *surf;
+ int st;
+ int w;
+ int h;
+ int format;
+ void *pixels;
+
+ pixels = NULL;
+ surf = NULL;
+ ren_tex = NULL;
+ format = SDL_PIXELFORMAT_RGBA32;
+
+ /* Get information about texture we want to save */
+ st = SDL_QueryTexture(tex, NULL, NULL, &w, &h);
+ if (st != 0)
+ {
+ SDL_Log("Failed querying texture: %s\n", SDL_GetError());
+ err++;
+ goto cleanup;
+ }
+
+ ren_tex = SDL_CreateTexture(ren, format, SDL_TEXTUREACCESS_TARGET, w, h);
+ if (!ren_tex)
+ {
+ SDL_Log("Failed creating render texture: %s\n", SDL_GetError());
+ err++;
+ goto cleanup;
+ }
+
+ /*
+ * Initialize our canvas, then copy texture to a target whose pixel data we
+ * can access
+ */
+ st = SDL_SetRenderTarget(ren, ren_tex);
+ if (st != 0)
+ {
+ SDL_Log("Failed setting render target: %s\n", SDL_GetError());
+ err++;
+ goto cleanup;
+ }
+ SDL_SetRenderDrawColor(ren, 0x00, 0x00, 0x00, 0x00);
+ SDL_RenderClear(ren);
+ st = SDL_RenderCopy(ren, tex, NULL, NULL);
+ if (st != 0)
+ {
+ SDL_Log("Failed copying texture data: %s\n", SDL_GetError());
+ err++;
+ goto cleanup;
+ }
+ /* Create buffer to hold texture data and load it */
+ pixels = malloc(w * h * SDL_BYTESPERPIXEL(format));
+ if (!pixels)
+ {
+ SDL_Log("Failed allocating memory\n");
+ err++;
+ goto cleanup;
+ }
+ st = SDL_RenderReadPixels(ren, NULL, format, pixels, w * SDL_BYTESPERPIXEL(format));
+ if (st != 0)
+ {
+ SDL_Log("Failed reading pixel data: %s\n", SDL_GetError());
+ err++;
+ goto cleanup;
+ }
+ /* Copy pixel data over to surface */
+ surf = SDL_CreateRGBSurfaceWithFormatFrom(pixels, w, h, SDL_BITSPERPIXEL(format), w * SDL_BYTESPERPIXEL(format), format);
+ if (!surf)
+ {
+ SDL_Log("Failed creating new surface: %s\n", SDL_GetError());
+ err++;
+ goto cleanup;
+ }
+ /* Save result to an image */
+ st = SDL_SaveBMP(surf, filename);
+ if (st != 0)
+ {
+ SDL_Log("Failed saving image: %s\n", SDL_GetError());
+ err++;
+ goto cleanup;
+ }
+ SDL_Log("Saved texture as BMP to \"%s\"\n", filename);
+
+cleanup:
+ SDL_FreeSurface(surf);
+ free(pixels);
+ SDL_DestroyTexture(ren_tex);
+ return err;
+}
+
+bool sdl_main_loop_ready()
+{
+ pthread_mutex_lock(&mu);
+ bool is_ready = ready;
+ pthread_mutex_unlock(&mu);
+
+ return is_ready;
+}
+
+keyboard_t sdl_getKeys()
+{
+ pthread_mutex_lock(&mu);
+ keyboard_t keys = sdl_keys;
+ pthread_mutex_unlock(&mu);
+
+ return keys;
+}
+
+/*
+ * SDL main loop. Due to macOS restrictions, this must run on the Main Thread.
+ */
+void sdl_task()
+{
+ pthread_mutex_lock(&mu);
+ ready = true;
+ pthread_mutex_unlock(&mu);
+
+ SDL_Event ev = { 0 };
+ while(!Radio_State.PowerOff)
+ {
+ keyboard_t key = 0;
+ if(SDL_PollEvent(&ev) == 1)
+ {
+ switch (ev.type)
+ {
+ case SDL_QUIT:
+ Radio_State.PowerOff = true;
+ break;
+ case SDL_KEYDOWN:
+ if (sdk_key_code_to_key(ev.key.keysym.sym, &key))
+ {
+ pthread_mutex_lock(&mu);
+ sdl_keys |= key;
+ pthread_mutex_unlock(&mu);
+ }
+ break;
+ case SDL_KEYUP:
+ if (sdk_key_code_to_key(ev.key.keysym.sym, &key))
+ {
+ pthread_mutex_lock(&mu);
+ sdl_keys ^= key;
+ pthread_mutex_unlock(&mu);
+ }
+ break;
+ }
+ if( ev.type == SDL_Screenshot_Event)
+ {
+ char *filename = (char *)ev.user.data1;
+ screenshot_display(filename);
+ free(ev.user.data1);
+ }
+ }
+
+ if (chan_can_recv(&fb_sync))
+ {
+ PIXEL_SIZE *pixels;
+ int pitch = 0;
+ if (SDL_LockTexture(displayTexture, NULL, (void **) &pixels, &pitch) < 0)
+ {
+ SDL_Log("SDL_lock failed: %s", SDL_GetError());
+ }
+
+ void *fb;
+ chan_recv(&fb_sync, &fb);
+ memcpy(pixels, fb, sizeof(PIXEL_SIZE) * SCREEN_HEIGHT * SCREEN_WIDTH);
+
+ SDL_UnlockTexture(displayTexture);
+ SDL_RenderCopy(renderer, displayTexture, NULL, NULL);
+ SDL_RenderPresent(renderer);
+ }
+ } /* while(!Radio_State.PowerOff) */
+
+ SDL_DestroyWindow(window);
+ SDL_Quit();
+}
+
+void init_sdl()
+{
+ pthread_mutex_init(&mu, NULL);
+
+ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0)
+ {
+ printf("SDL video init error!!\n");
+ exit(1);
+ }
+
+ // Register an SDL custom event type to handle screenshot requests
+ SDL_Screenshot_Event = SDL_RegisterEvents(1);
+
+ chan_init(&fb_sync);
+
+ window = SDL_CreateWindow("OpenRTX",
+ SDL_WINDOWPOS_UNDEFINED,
+ SDL_WINDOWPOS_UNDEFINED,
+ SCREEN_WIDTH * 3, SCREEN_HEIGHT * 3,
+ SDL_WINDOW_SHOWN );
+
+ renderer = SDL_CreateRenderer(window, -1, 0);
+ SDL_RenderSetLogicalSize(renderer, SCREEN_WIDTH, SCREEN_HEIGHT);
+ displayTexture = SDL_CreateTexture(renderer,
+ PIXEL_FORMAT,
+ SDL_TEXTUREACCESS_STREAMING,
+ SCREEN_WIDTH,
+ SCREEN_HEIGHT);
+ SDL_RenderClear(renderer);
+ SDL_RenderCopy(renderer, displayTexture, NULL, NULL);
+ SDL_RenderPresent(renderer);
+}
diff --git a/platform/targets/linux/emulator/sdl_engine.h b/platform/targets/linux/emulator/sdl_engine.h
new file mode 100644
index 00000000..2703265c
--- /dev/null
+++ b/platform/targets/linux/emulator/sdl_engine.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+ * Copyright (C) 2021 by Alessio Caiazza IU5BON *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, see *
+ ***************************************************************************/
+#ifndef SDL_ENGINE_H
+#define SDL_ENGINE_H
+
+#include "chan.h"
+#include
+#include
+
+/*
+ * Screen dimensions, adjust basing on the size of the screen you need to
+ * emulate
+ */
+#ifndef SCREEN_WIDTH
+#define SCREEN_WIDTH 160
+#endif
+
+#ifndef SCREEN_HEIGHT
+#define SCREEN_HEIGHT 128
+#endif
+
+#ifdef PIX_FMT_RGB565
+#define PIXEL_FORMAT SDL_PIXELFORMAT_RGB565
+#define PIXEL_SIZE uint16_t
+#else
+#define PIXEL_FORMAT SDL_PIXELFORMAT_ARGB8888
+#define PIXEL_SIZE uint32_t
+#endif
+
+/* Initialize the SDL engine. Must be called in the Main Thread */
+void init_sdl();
+/* SDL main loop. Must be called in the Main Thread */
+void sdl_task();
+/* Thread-safe check to verify if the application entered the SDL main loop. */
+bool sdl_main_loop_ready();
+
+#endif /* SDL_ENGINE_H */