Refactor SDL/FreeRTOS implementation to support macOS simulator properly (#653)

- Run SDL from main() and place FreeRTOS in separate thread. This fixes macOS support.
- Updated GitHub Actions to publish macOS simulator build for testing, updated amd64 to x86_64 for consistent naming.
This commit is contained in:
Ken Van Hoeylandt
2026-09-20 23:12:42 +02:00
committed by Adolfo Reyna
parent 89e8baf517
commit 72089f74f3
25 changed files with 748 additions and 373 deletions
+70 -46
View File
@@ -7,11 +7,16 @@
#include <SDL2/SDL.h>
#include <cstdlib>
#include <mutex>
namespace {
constexpr size_t KEY_QUEUE_CAPACITY = 32;
// Written by sdl_input_pump() on the real main thread, read by sdl_input_get_pointer_state()/
// sdl_input_pop_key()/sdl_input_has_queued_key() on the lvgl task.
std::mutex state_mutex;
SdlPointerState pointer_state = { 0, 0, false };
} // namespace
@@ -26,6 +31,7 @@ uint32_t touch_override_until_tick = 0;
#define SIM_TOUCH_HOLD_MS 1500
extern "C" void sdl_input_set_touch_override(int32_t x, int32_t y, bool pressed) {
std::lock_guard<std::mutex> lock(state_mutex);
touch_override.x = x;
touch_override.y = y;
touch_override.pressed = pressed;
@@ -36,6 +42,7 @@ extern "C" void sdl_input_set_touch_override(int32_t x, int32_t y, bool pressed)
}
extern "C" void sdl_input_clear_touch_override(void) {
std::lock_guard<std::mutex> lock(state_mutex);
touch_override_active = false;
touch_override.pressed = false;
}
@@ -101,58 +108,73 @@ uint32_t keycode_to_key(SDL_Keycode sdl_key, bool shift) {
} // namespace
void sdl_input_pump() {
if (!text_input_started) {
SDL_StartTextInput();
text_input_started = true;
// exit() must run with state_mutex unlocked: it never returns, so a lock_guard held across it
// would never release the mutex, hanging any other thread that later calls into this file's
// other functions (all of which lock state_mutex) while exit() tears the process down.
bool quit_requested = false;
{
std::lock_guard<std::mutex> lock(state_mutex);
if (!text_input_started) {
SDL_StartTextInput();
text_input_started = true;
}
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_MOUSEMOTION:
set_pointer_position(event.motion.x, event.motion.y);
break;
case SDL_MOUSEBUTTONDOWN:
if (event.button.button == SDL_BUTTON_LEFT) {
// event.button.x/y can be stale immediately after a window resize (an
// SDL/X11 event-queue quirk - confirmed by comparing against a live
// SDL_GetWindowSize() at the same instant). SDL_GetMouseState() queries the
// OS for the current pointer position directly, sidestepping that entirely.
int live_x, live_y;
SDL_GetMouseState(&live_x, &live_y);
set_pointer_position(live_x, live_y);
pointer_state.pressed = true;
}
break;
case SDL_MOUSEBUTTONUP:
if (event.button.button == SDL_BUTTON_LEFT) {
pointer_state.pressed = false;
}
break;
case SDL_KEYDOWN:
push_key(keycode_to_key(event.key.keysym.sym, (event.key.keysym.mod & KMOD_SHIFT) != 0));
break;
case SDL_TEXTINPUT:
// ASCII only (first byte of event.text.text) - sufficient for a simulator keyboard.
push_key(static_cast<uint8_t>(event.text.text[0]));
break;
case SDL_WINDOWEVENT:
// Resizing doesn't change what LVGL last rendered, only how large it should
// appear - re-present the existing frame at the new scale immediately, rather
// than leaving stale-looking content on screen until the next LVGL-driven flush.
if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
sdl_display_present_now();
}
break;
case SDL_QUIT:
quit_requested = true;
break;
default:
break;
}
}
}
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_MOUSEMOTION:
set_pointer_position(event.motion.x, event.motion.y);
break;
case SDL_MOUSEBUTTONDOWN:
if (event.button.button == SDL_BUTTON_LEFT) {
// event.button.x/y can be stale immediately after a window resize (an
// SDL/X11 event-queue quirk - confirmed by comparing against a live
// SDL_GetWindowSize() at the same instant). SDL_GetMouseState() queries the
// OS for the current pointer position directly, sidestepping that entirely.
int live_x, live_y;
SDL_GetMouseState(&live_x, &live_y);
set_pointer_position(live_x, live_y);
pointer_state.pressed = true;
}
break;
case SDL_MOUSEBUTTONUP:
if (event.button.button == SDL_BUTTON_LEFT) {
pointer_state.pressed = false;
}
break;
case SDL_KEYDOWN:
push_key(keycode_to_key(event.key.keysym.sym, (event.key.keysym.mod & KMOD_SHIFT) != 0));
break;
case SDL_TEXTINPUT:
// ASCII only (first byte of event.text.text) - sufficient for a simulator keyboard.
push_key(static_cast<uint8_t>(event.text.text[0]));
break;
case SDL_WINDOWEVENT:
// Resizing doesn't change what LVGL last rendered, only how large it should
// appear - re-present the existing frame at the new scale immediately, rather
// than leaving stale-looking content on screen until the next LVGL-driven flush.
if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
sdl_display_present_now();
}
break;
case SDL_QUIT:
exit(0);
default:
break;
}
if (quit_requested) {
exit(0);
}
}
void sdl_input_get_pointer_state(SdlPointerState* out_state) {
std::lock_guard<std::mutex> lock(state_mutex);
if (touch_override_active) {
// Auto-release: viewer sends press only; LVGL needs press then release
// to register a click. Hold long enough for several indev polls.
@@ -168,6 +190,7 @@ void sdl_input_get_pointer_state(SdlPointerState* out_state) {
}
bool sdl_input_pop_key(uint32_t* out_key) {
std::lock_guard<std::mutex> lock(state_mutex);
if (key_queue_count == 0) {
return false;
}
@@ -178,5 +201,6 @@ bool sdl_input_pop_key(uint32_t* out_key) {
}
bool sdl_input_has_queued_key() {
std::lock_guard<std::mutex> lock(state_mutex);
return key_queue_count > 0;
}