Remove old HAL components and refactored GPS-related code (#583)

- Added generic GPS/GNSS support with device detection, configuration, and persistent settings.
- Improved device and module lifecycle management.
- Added flexible filesystem locking support for displays and storage.
- Improved display-idle and keyboard backlight handling.
- Updated architecture, driver, module, testing, and licensing documentation.
- Removed old HAL device and related code.
This commit is contained in:
Ken Van Hoeylandt
2026-07-25 17:20:17 +02:00
committed by GitHub
parent 29e80cfd65
commit 2a2558b29a
173 changed files with 3855 additions and 5445 deletions
@@ -0,0 +1,160 @@
// SPDX-License-Identifier: Apache-2.0
#include "sdl_display.h"
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/display.h>
#include <tactility/log.h>
#include <tactility/module.h>
#include <SDL2/SDL.h>
#include <cstdlib>
constexpr auto* TAG = "SdlDisplay";
#define GET_CONFIG(device) (static_cast<const SdlDisplayConfig*>((device)->config))
struct SdlDisplayInternal {
SDL_Window* window;
SDL_Renderer* renderer;
SDL_Texture* texture;
};
// region Driver lifecycle
static error_t start(Device* device) {
const auto* config = GET_CONFIG(device);
auto* internal = static_cast<SdlDisplayInternal*>(malloc(sizeof(SdlDisplayInternal)));
if (internal == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
LOG_E(TAG, "SDL_InitSubSystem failed: %s", SDL_GetError());
free(internal);
return ERROR_RESOURCE;
}
internal->window = SDL_CreateWindow(
"Tactility",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
config->horizontal_resolution, config->vertical_resolution,
SDL_WINDOW_SHOWN
);
internal->renderer = internal->window != nullptr
? SDL_CreateRenderer(internal->window, -1, SDL_RENDERER_ACCELERATED)
: nullptr;
internal->texture = internal->renderer != nullptr
? SDL_CreateTexture(internal->renderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING,
config->horizontal_resolution, config->vertical_resolution)
: nullptr;
if (internal->window == nullptr || internal->renderer == nullptr || internal->texture == nullptr) {
LOG_E(TAG, "Failed to create SDL window: %s", SDL_GetError());
if (internal->texture != nullptr) SDL_DestroyTexture(internal->texture);
if (internal->renderer != nullptr) SDL_DestroyRenderer(internal->renderer);
if (internal->window != nullptr) SDL_DestroyWindow(internal->window);
SDL_QuitSubSystem(SDL_INIT_VIDEO);
free(internal);
return ERROR_RESOURCE;
}
device_set_driver_data(device, internal);
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* internal = static_cast<SdlDisplayInternal*>(device_get_driver_data(device));
SDL_DestroyTexture(internal->texture);
SDL_DestroyRenderer(internal->renderer);
SDL_DestroyWindow(internal->window);
SDL_QuitSubSystem(SDL_INIT_VIDEO);
free(internal);
device_set_driver_data(device, nullptr);
return ERROR_NONE;
}
// endregion
// region DisplayApi
static error_t sdl_display_reset(Device*) { return ERROR_NONE; }
static error_t sdl_display_init(Device*) { return ERROR_NONE; }
static error_t sdl_display_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
auto* internal = static_cast<SdlDisplayInternal*>(device_get_driver_data(device));
SDL_Rect rect = { x_start, y_start, x_end - x_start, y_end - y_start };
// RGB565 = 2 bytes/pixel.
if (SDL_UpdateTexture(internal->texture, &rect, color_data, (x_end - x_start) * 2) != 0) {
return ERROR_RESOURCE;
}
SDL_RenderClear(internal->renderer);
SDL_RenderCopy(internal->renderer, internal->texture, nullptr, nullptr);
SDL_RenderPresent(internal->renderer);
return ERROR_NONE;
}
static enum DisplayColorFormat sdl_display_get_color_format(Device*) {
return DISPLAY_COLOR_FORMAT_RGB565;
}
static uint16_t sdl_display_get_resolution_x(Device* device) {
return GET_CONFIG(device)->horizontal_resolution;
}
static uint16_t sdl_display_get_resolution_y(Device* device) {
return GET_CONFIG(device)->vertical_resolution;
}
static void sdl_display_get_frame_buffer(Device*, uint8_t, void** out_buffer) {
*out_buffer = nullptr;
}
static uint8_t sdl_display_get_frame_buffer_count(Device*) {
return 0;
}
// endregion
static const DisplayApi sdl_display_api = {
.capabilities = 0,
.reset = sdl_display_reset,
.init = sdl_display_init,
.draw_bitmap = sdl_display_draw_bitmap,
.mirror = nullptr,
.swap_xy = nullptr,
.get_swap_xy = nullptr,
.get_mirror_x = nullptr,
.get_mirror_y = nullptr,
.set_gap = nullptr,
.get_gap_x = nullptr,
.get_gap_y = nullptr,
.invert_color = nullptr,
.disp_on_off = nullptr,
.disp_sleep = nullptr,
.get_color_format = sdl_display_get_color_format,
.get_resolution_x = sdl_display_get_resolution_x,
.get_resolution_y = sdl_display_get_resolution_y,
.get_frame_buffer = sdl_display_get_frame_buffer,
.get_frame_buffer_count = sdl_display_get_frame_buffer_count,
.get_backlight = nullptr,
.has_capability = nullptr,
};
extern Module simulator_module;
Driver sdl_display_driver = {
.name = "sdl-display",
.compatible = (const char*[]) { "tactility,sdl-display", nullptr },
.start_device = start,
.stop_device = stop,
.api = &sdl_display_api,
.device_type = &DISPLAY_TYPE,
.owner = &simulator_module,
.internal = nullptr
};
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
struct SdlDisplayConfig {
uint16_t horizontal_resolution;
uint16_t vertical_resolution;
};
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: Apache-2.0
#include "sdl_input.h"
#include <lvgl.h>
#include <SDL2/SDL.h>
#include <cstdlib>
namespace {
constexpr size_t KEY_QUEUE_CAPACITY = 32;
SdlPointerState pointer_state = { 0, 0, false };
uint32_t key_queue[KEY_QUEUE_CAPACITY];
size_t key_queue_head = 0;
size_t key_queue_count = 0;
bool text_input_started = false;
void push_key(uint32_t key) {
if (key == 0 || key_queue_count >= KEY_QUEUE_CAPACITY) {
return;
}
key_queue[(key_queue_head + key_queue_count) % KEY_QUEUE_CAPACITY] = key;
key_queue_count++;
}
// Mirrors LVGL's own lv_sdl_keyboard.c keycode_to_ctrl_key(): maps navigation/control keys to
// LV_KEY_* constants. Printable characters arrive separately via SDL_TEXTINPUT.
uint32_t keycode_to_key(SDL_Keycode sdl_key) {
switch (sdl_key) {
case SDLK_RIGHT: return LV_KEY_RIGHT;
case SDLK_LEFT: return LV_KEY_LEFT;
case SDLK_UP: return LV_KEY_UP;
case SDLK_DOWN: return LV_KEY_DOWN;
case SDLK_ESCAPE: return LV_KEY_ESC;
case SDLK_BACKSPACE: return LV_KEY_BACKSPACE;
case SDLK_DELETE: return LV_KEY_DEL;
case SDLK_RETURN:
case SDLK_KP_ENTER: return LV_KEY_ENTER;
case SDLK_TAB: return LV_KEY_NEXT;
case SDLK_HOME: return LV_KEY_HOME;
case SDLK_END: return LV_KEY_END;
default: return 0;
}
}
}
void sdl_input_pump() {
if (!text_input_started) {
SDL_StartTextInput();
text_input_started = true;
}
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_MOUSEMOTION:
pointer_state.x = event.motion.x;
pointer_state.y = event.motion.y;
break;
case SDL_MOUSEBUTTONDOWN:
if (event.button.button == SDL_BUTTON_LEFT) {
pointer_state.x = event.button.x;
pointer_state.y = event.button.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));
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_QUIT:
exit(0);
default:
break;
}
}
}
void sdl_input_get_pointer_state(SdlPointerState* out_state) {
*out_state = pointer_state;
}
bool sdl_input_pop_key(uint32_t* out_key) {
if (key_queue_count == 0) {
return false;
}
*out_key = key_queue[key_queue_head];
key_queue_head = (key_queue_head + 1) % KEY_QUEUE_CAPACITY;
key_queue_count--;
return true;
}
bool sdl_input_has_queued_key() {
return key_queue_count > 0;
}
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
/**
* @brief Latest pointer (mouse) state as tracked by sdl_input_pump().
*/
struct SdlPointerState {
int32_t x;
int32_t y;
bool pressed;
};
/**
* @brief Drains all pending SDL events exactly once, updating the pointer state and key queue
* below. Safe to call from both the sdl-pointer and sdl-keyboard drivers' polling functions:
* SDL_PollEvent() drains a single global queue, so whichever driver is polled first on a given
* LVGL indev tick pumps events for both.
*/
void sdl_input_pump(void);
/**
* @brief Gets the pointer state as of the most recent sdl_input_pump() call.
*/
void sdl_input_get_pointer_state(struct SdlPointerState* out_state);
/**
* @brief Pops the next queued key event (produced by SDL_KEYDOWN/SDL_TEXTINPUT during
* sdl_input_pump()).
* @retval false when no key event is pending
*/
bool sdl_input_pop_key(uint32_t* out_key);
/**
* @brief Returns true if another key event is queued after the one just popped.
*/
bool sdl_input_has_queued_key(void);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: Apache-2.0
#include "sdl_input.h"
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/keyboard.h>
#include <tactility/module.h>
// region Driver lifecycle
static error_t start(Device*) { return ERROR_NONE; }
static error_t stop(Device*) { return ERROR_NONE; }
// endregion
// region KeyboardApi
static error_t sdl_keyboard_read_key(Device*, KeyboardKeyData* data) {
sdl_input_pump();
uint32_t key = 0;
if (sdl_input_pop_key(&key)) {
data->key = key;
data->pressed = true;
data->continue_reading = sdl_input_has_queued_key();
} else {
data->key = 0;
data->pressed = false;
data->continue_reading = false;
}
return ERROR_NONE;
}
// endregion
static const KeyboardApi sdl_keyboard_api = {
.read_key = sdl_keyboard_read_key,
.get_backlight = nullptr,
};
extern Module simulator_module;
Driver sdl_keyboard_driver = {
.name = "sdl-keyboard",
.compatible = (const char*[]) { "tactility,sdl-keyboard", nullptr },
.start_device = start,
.stop_device = stop,
.api = &sdl_keyboard_api,
.device_type = &KEYBOARD_TYPE,
.owner = &simulator_module,
.internal = nullptr
};
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: Apache-2.0
#include "sdl_input.h"
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/pointer.h>
#include <tactility/module.h>
// region Driver lifecycle
static error_t start(Device*) { return ERROR_NONE; }
static error_t stop(Device*) { return ERROR_NONE; }
// endregion
// region PointerApi
static error_t sdl_pointer_read_data(Device*, TickType_t) {
sdl_input_pump();
return ERROR_NONE;
}
static bool sdl_pointer_get_touched_points(Device*, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_count, uint8_t max_point_count) {
SdlPointerState state;
sdl_input_get_pointer_state(&state);
if (!state.pressed || max_point_count == 0) {
*point_count = 0;
return false;
}
x[0] = static_cast<uint16_t>(state.x);
y[0] = static_cast<uint16_t>(state.y);
if (strength != nullptr) {
strength[0] = 0xFFFF;
}
*point_count = 1;
return true;
}
// endregion
static const PointerApi sdl_pointer_api = {
.enter_sleep = nullptr,
.exit_sleep = nullptr,
.read_data = sdl_pointer_read_data,
.get_touched_points = sdl_pointer_get_touched_points,
.set_swap_xy = nullptr,
.get_swap_xy = nullptr,
.set_mirror_x = nullptr,
.get_mirror_x = nullptr,
.set_mirror_y = nullptr,
.get_mirror_y = nullptr,
};
extern Module simulator_module;
Driver sdl_pointer_driver = {
.name = "sdl-pointer",
.compatible = (const char*[]) { "tactility,sdl-pointer", nullptr },
.start_device = start,
.stop_device = stop,
.api = &sdl_pointer_api,
.device_type = &POINTER_TYPE,
.owner = &simulator_module,
.internal = nullptr
};