Files
2026-08-04 11:55:02 -07:00

124 lines
3.3 KiB
C

/*
* Host implementation of the firmware's lvgl_port.h contract.
*
* The firmware version (main/ui/lvgl_port.c) is built on FreeRTOS tasks and
* esp_timer. This file provides the same six entry points on pthreads so that
* main/ui/ui_manager.c -- the file whose layout we actually iterate on --
* compiles and runs unchanged on the desktop.
*
* The one deliberate behavioural difference is lvgl_port_start(): on the
* device it spawns the task that calls lv_timer_handler(), whereas here the
* simulator's own main loop drives LVGL. See the comment on that function.
*/
#include <pthread.h>
#include <stdbool.h>
#include <time.h>
#include "esp_log.h"
#include "lvgl.h"
#include "lvgl_port.h"
static const char *TAG = "lvgl_port_sim";
static pthread_mutex_t lvgl_mutex;
static bool mutex_ready = false;
static volatile bool paused = false;
/* LVGL's millisecond time base. The firmware drives this from a 2ms esp_timer
* calling lv_tick_inc(); on the host a monotonic clock read is both simpler
* and more accurate. */
static uint32_t tick_get_cb(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint32_t)(ts.tv_sec * 1000U + ts.tv_nsec / 1000000U);
}
esp_err_t lvgl_port_init(void)
{
/*
* Recursive, matching the device: lv_timer_handler() runs callbacks that
* may re-enter code which also takes the lock. Keeping the same semantics
* here means a deadlock introduced in shared UI code reproduces in the
* simulator instead of only showing up on hardware.
*/
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
if (pthread_mutex_init(&lvgl_mutex, &attr) != 0) {
pthread_mutexattr_destroy(&attr);
return ESP_ERR_NO_MEM;
}
pthread_mutexattr_destroy(&attr);
mutex_ready = true;
lv_init();
lv_tick_set_cb(tick_get_cb);
ESP_LOGI(TAG, "LVGL %d.%d.%d initialized (simulator)",
lv_version_major(), lv_version_minor(), lv_version_patch());
return ESP_OK;
}
/*
* No-op by design.
*
* On the device this starts the tick timer and the LVGL service task. Here the
* tick is a clock callback installed in lvgl_port_init(), and lv_timer_handler()
* is called from the simulator's main loop -- running it from a second thread
* as well would mean two threads rendering into one framebuffer.
*
* It still exists so that shared start-up code can call it unconditionally.
*/
esp_err_t lvgl_port_start(void)
{
return ESP_OK;
}
void lvgl_port_pause(void)
{
paused = true;
}
void lvgl_port_resume(void)
{
paused = false;
}
bool lvgl_port_is_paused(void)
{
return paused;
}
bool lvgl_port_lock(int timeout_ms)
{
if (!mutex_ready) {
return false;
}
if (timeout_ms < 0) {
return pthread_mutex_lock(&lvgl_mutex) == 0;
}
struct timespec deadline;
clock_gettime(CLOCK_REALTIME, &deadline);
deadline.tv_sec += timeout_ms / 1000;
deadline.tv_nsec += (long)(timeout_ms % 1000) * 1000000L;
if (deadline.tv_nsec >= 1000000000L) {
deadline.tv_sec++;
deadline.tv_nsec -= 1000000000L;
}
return pthread_mutex_timedlock(&lvgl_mutex, &deadline) == 0;
}
void lvgl_port_unlock(void)
{
if (mutex_ready) {
pthread_mutex_unlock(&lvgl_mutex);
}
}