Added Lilygo T-Deck support & more (#4)
* added lilygo t-deck restructured boards implemented HardwareConfig implemented lilygo t-deck lcd and touch drivers added sdkconfig defaults for supported boards * cleanup * added esp32s3 job * build job names updated * wip * partial revert * update readme and build.yml * updated build.yaml with fix for quotes * use esp-idf 5.1.2 * improvements and fixes * fixes for display code * made config const * various improvements
This commit is contained in:
committed by
GitHub
parent
eed990217f
commit
8336316133
@@ -1,5 +0,0 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "."
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES nanobake esp_lcd_touch_cst816s esp_lcd_ili9341
|
||||
)
|
||||
@@ -1,4 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "board_2432s024_display.h"
|
||||
#include "board_2432s024_touch.h"
|
||||
@@ -1,130 +0,0 @@
|
||||
#include "board_2432s024_display.h"
|
||||
|
||||
#include "esp_lcd_ili9341.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
#define TAG "2432s024_ili9341"
|
||||
|
||||
static SemaphoreHandle_t refresh_finish = NULL;
|
||||
|
||||
#define LCD_SPI_HOST SPI2_HOST
|
||||
#define LCD_PIN_SCLK GPIO_NUM_14
|
||||
#define LCD_PIN_MOSI GPIO_NUM_13
|
||||
#define LCD_PIN_CS GPIO_NUM_15
|
||||
#define LCD_PIN_DC GPIO_NUM_2
|
||||
#define LCD_PIN_BACKLIGHT GPIO_NUM_27
|
||||
|
||||
#define LCD_HORIZONTAL_RESOLUTION 240
|
||||
#define LCD_VERTICAL_RESOLUTION 320
|
||||
#define LCD_BITS_PER_PIXEL 16
|
||||
#define LCD_DRAW_BUFFER_HEIGHT 80
|
||||
|
||||
IRAM_ATTR static bool prv_on_color_trans_done(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_io_event_data_t* edata, void* user_ctx) {
|
||||
BaseType_t need_yield = pdFALSE;
|
||||
xSemaphoreGiveFromISR(refresh_finish, &need_yield);
|
||||
return (need_yield == pdTRUE);
|
||||
}
|
||||
|
||||
static bool create_display_device(DisplayDevice* display) {
|
||||
ESP_LOGI(TAG, "creating display");
|
||||
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = BIT64(LCD_PIN_BACKLIGHT),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&io_conf);
|
||||
|
||||
size_t draw_buffer_size = LCD_HORIZONTAL_RESOLUTION * LCD_DRAW_BUFFER_HEIGHT * (LCD_BITS_PER_PIXEL / 8);
|
||||
const spi_bus_config_t bus_config = ILI9341_PANEL_BUS_SPI_CONFIG(
|
||||
LCD_PIN_SCLK,
|
||||
LCD_PIN_MOSI,
|
||||
draw_buffer_size
|
||||
);
|
||||
|
||||
if (spi_bus_initialize(LCD_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO) != ESP_OK) {
|
||||
ESP_LOGD(TAG, "spi bus init failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
const esp_lcd_panel_io_spi_config_t panel_io_config = ILI9341_PANEL_IO_SPI_CONFIG(
|
||||
LCD_PIN_CS,
|
||||
LCD_PIN_DC,
|
||||
prv_on_color_trans_done,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_SPI_HOST, &panel_io_config, &display->io_handle) != ESP_OK) {
|
||||
ESP_LOGD(TAG, "failed to create panel");
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "install driver");
|
||||
const esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = GPIO_NUM_NC,
|
||||
.rgb_endian = LCD_RGB_ENDIAN_RGB,
|
||||
.bits_per_pixel = LCD_BITS_PER_PIXEL,
|
||||
};
|
||||
|
||||
if (esp_lcd_new_panel_ili9341(display->io_handle, &panel_config, &display->display_handle) != ESP_OK) {
|
||||
ESP_LOGD(TAG, "failed to create ili9341");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (esp_lcd_panel_reset(display->display_handle) != ESP_OK) {
|
||||
ESP_LOGD(TAG, "failed to reset panel");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (esp_lcd_panel_init(display->display_handle) != ESP_OK) {
|
||||
ESP_LOGD(TAG, "failed to init panel");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (esp_lcd_panel_mirror(display->display_handle, true, false) != ESP_OK) {
|
||||
ESP_LOGD(TAG, "failed to set panel to mirror");
|
||||
display->mirror_x = true;
|
||||
display->mirror_y = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (esp_lcd_panel_swap_xy(display->display_handle, false) != ESP_OK) {
|
||||
ESP_LOGD(TAG, "failed to set panel xy swap");
|
||||
display->swap_xy = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (esp_lcd_panel_disp_on_off(display->display_handle, true) != ESP_OK) {
|
||||
ESP_LOGD(TAG, "failed to turn display on");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gpio_set_level(LCD_PIN_BACKLIGHT, 1) != ESP_OK) {
|
||||
ESP_LOGD(TAG, "failed to turn backlight on");
|
||||
return false;
|
||||
}
|
||||
|
||||
display->horizontal_resolution = LCD_HORIZONTAL_RESOLUTION;
|
||||
display->vertical_resolution = LCD_VERTICAL_RESOLUTION;
|
||||
display->draw_buffer_height = LCD_DRAW_BUFFER_HEIGHT;
|
||||
display->bits_per_pixel = LCD_BITS_PER_PIXEL;
|
||||
display->monochrome = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DisplayDriver board_2432s024_create_display_driver() {
|
||||
return (DisplayDriver) {
|
||||
.name = "ili9341_2432s024",
|
||||
.create_display_device = &create_display_device
|
||||
};
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "display.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern DisplayDriver board_2432s024_create_display_driver();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,72 +0,0 @@
|
||||
#include "board_2432s024_touch.h"
|
||||
|
||||
#include "esp_lcd_touch_cst816s.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "driver/i2c.h"
|
||||
|
||||
#define CST816_I2C_PORT (0)
|
||||
|
||||
#define TAG "2432s024_cst816"
|
||||
|
||||
static bool create_touch_device(esp_lcd_panel_io_handle_t* io_handle, esp_lcd_touch_handle_t* touch_handle) {
|
||||
ESP_LOGI(TAG, "creating touch");
|
||||
|
||||
const i2c_config_t i2c_conf = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = GPIO_NUM_33,
|
||||
.sda_pullup_en = GPIO_PULLUP_DISABLE,
|
||||
.scl_io_num = GPIO_NUM_32,
|
||||
.scl_pullup_en = GPIO_PULLUP_DISABLE,
|
||||
.master.clk_speed = 400000
|
||||
};
|
||||
|
||||
if (i2c_param_config(CST816_I2C_PORT, &i2c_conf) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2c config failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (i2c_driver_install(CST816_I2C_PORT, i2c_conf.mode, 0, 0, 0) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2c driver install failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
const esp_lcd_panel_io_i2c_config_t touch_io_config = ESP_LCD_TOUCH_IO_I2C_CST816S_CONFIG();
|
||||
|
||||
if (esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)CST816_I2C_PORT, &touch_io_config, io_handle) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_lcd_panel creation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "create_touch");
|
||||
esp_lcd_touch_config_t config = {
|
||||
.x_max = 240,
|
||||
.y_max = 320,
|
||||
.rst_gpio_num = GPIO_NUM_25,
|
||||
.int_gpio_num = GPIO_NUM_21,
|
||||
.levels = {
|
||||
.reset = 0,
|
||||
.interrupt = 0,
|
||||
},
|
||||
.flags = {
|
||||
.swap_xy = 0,
|
||||
.mirror_x = 0,
|
||||
.mirror_y = 0,
|
||||
},
|
||||
.interrupt_callback = NULL,
|
||||
};
|
||||
|
||||
if (esp_lcd_touch_new_i2c_cst816s(*io_handle, &config, touch_handle) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_lcd_touch_new_i2c_cst816s failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TouchDriver board_2432s024_create_touch_driver() {
|
||||
return (TouchDriver) {
|
||||
.name = "cst816s_2432s024",
|
||||
.create_touch_device = &create_touch_device
|
||||
};
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "touch.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern TouchDriver board_2432s024_create_touch_driver();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -63,11 +63,6 @@ typedef struct {
|
||||
* Non-blocking method to create the GUI
|
||||
*/
|
||||
const AppOnShow _Nullable on_show;
|
||||
|
||||
/**
|
||||
* Callstack size. If you get a stackoverflow, then consider increasing this value.
|
||||
*/
|
||||
const AppStackSize stack_size;
|
||||
} AppManifest;
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -65,7 +65,7 @@ FURI_NORETURN void __furi_halt_implementation();
|
||||
do { \
|
||||
if (!(__e)) { \
|
||||
ESP_LOGE("check", "%s", #__e); \
|
||||
__furi_crash(__m); \
|
||||
__furi_crash(#__m); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
@@ -83,7 +83,7 @@ FURI_NORETURN void __furi_halt_implementation();
|
||||
do { \
|
||||
if (!(__e)) { \
|
||||
ESP_LOGE("assert", "%s", #__e); \
|
||||
__furi_crash(__m); \
|
||||
__furi_crash(#__m); \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
|
||||
@@ -116,7 +116,11 @@ uint32_t furi_event_flag_wait(
|
||||
}
|
||||
|
||||
rflags = xEventGroupWaitBits(
|
||||
hEventGroup, (EventBits_t)flags, exit_clr, wait_all, (TickType_t)timeout
|
||||
hEventGroup,
|
||||
(EventBits_t)flags,
|
||||
exit_clr,
|
||||
wait_all,
|
||||
(TickType_t)timeout
|
||||
);
|
||||
|
||||
if (options & FuriFlagWaitAll) {
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
/**
|
||||
* @file kernel.h
|
||||
* Furi Kernel primitives
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "furi_core_types.h"
|
||||
|
||||
#define configTICK_RATE_HZ_RAW 1000
|
||||
#define configTICK_RATE_HZ_RAW CONFIG_FREERTOS_HZ
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
||||
@@ -13,13 +13,14 @@ LIST_DEF(FuriPubSubSubscriptionList, FuriPubSubSubscription, M_POD_OPLIST);
|
||||
|
||||
struct FuriPubSub {
|
||||
FuriPubSubSubscriptionList_t items;
|
||||
// TODO: replace recursive mutex with semaphore
|
||||
FuriMutex* mutex;
|
||||
};
|
||||
|
||||
FuriPubSub* furi_pubsub_alloc() {
|
||||
FuriPubSub* pubsub = malloc(sizeof(FuriPubSub));
|
||||
|
||||
pubsub->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
pubsub->mutex = furi_mutex_alloc(FuriMutexTypeRecursive);
|
||||
furi_assert(pubsub->mutex);
|
||||
|
||||
FuriPubSubSubscriptionList_init(pubsub->items);
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
#include "mutex.h"
|
||||
#include "m-dict.h"
|
||||
#include "m_cstr_dup.h"
|
||||
#include "log.h"
|
||||
|
||||
#define TAG "record"
|
||||
|
||||
#define FURI_RECORD_FLAG_READY (0x1)
|
||||
|
||||
@@ -37,7 +40,7 @@ static void furi_record_erase(const char* name, FuriRecordData* record_data) {
|
||||
|
||||
void furi_record_init() {
|
||||
furi_record = malloc(sizeof(FuriRecord));
|
||||
furi_record->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
furi_record->mutex = furi_mutex_alloc(FuriMutexTypeRecursive);
|
||||
furi_check(furi_record->mutex);
|
||||
FuriRecordDataDict_init(furi_record->records);
|
||||
}
|
||||
@@ -111,6 +114,7 @@ bool furi_record_destroy(const char* name) {
|
||||
}
|
||||
|
||||
void* furi_record_open(const char* name) {
|
||||
furi_assert(name);
|
||||
furi_assert(furi_record);
|
||||
|
||||
furi_record_lock();
|
||||
@@ -134,6 +138,7 @@ void* furi_record_open(const char* name) {
|
||||
}
|
||||
|
||||
void furi_record_close(const char* name) {
|
||||
furi_assert(name);
|
||||
furi_assert(furi_record);
|
||||
|
||||
furi_record_lock();
|
||||
|
||||
@@ -51,7 +51,7 @@ struct FuriThread {
|
||||
|
||||
// Keep all non-alignable byte types in one place,
|
||||
// this ensures that the size of this structure is minimal
|
||||
bool is_service;
|
||||
bool is_static;
|
||||
bool heap_trace_enabled;
|
||||
|
||||
configSTACK_DEPTH_TYPE stack_size;
|
||||
@@ -113,7 +113,7 @@ static void furi_thread_body(void* context) {
|
||||
|
||||
furi_assert(thread->state == FuriThreadStateRunning);
|
||||
|
||||
if (thread->is_service) {
|
||||
if (thread->is_static) {
|
||||
ESP_LOGI(
|
||||
TAG,
|
||||
"%s service thread TCB memory will not be reclaimed",
|
||||
@@ -135,7 +135,7 @@ FuriThread* furi_thread_alloc() {
|
||||
// TODO: create default struct instead of using memset()
|
||||
memset(thread, 0, sizeof(FuriThread));
|
||||
thread->output.buffer = furi_string_alloc();
|
||||
thread->is_service = false;
|
||||
thread->is_static = false;
|
||||
|
||||
FuriThread* parent = NULL;
|
||||
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
|
||||
@@ -207,8 +207,8 @@ void furi_thread_set_appid(FuriThread* thread, const char* appid) {
|
||||
thread->appid = appid ? strdup(appid) : NULL;
|
||||
}
|
||||
|
||||
void furi_thread_mark_as_service(FuriThread* thread) {
|
||||
thread->is_service = true;
|
||||
void furi_thread_mark_as_static(FuriThread* thread) {
|
||||
thread->is_static = true;
|
||||
}
|
||||
|
||||
bool furi_thread_mark_is_service(FuriThreadId thread_id) {
|
||||
@@ -216,7 +216,7 @@ bool furi_thread_mark_is_service(FuriThreadId thread_id) {
|
||||
assert(!FURI_IS_IRQ_MODE() && (hTask != NULL));
|
||||
FuriThread* thread = (FuriThread*)pvTaskGetThreadLocalStoragePointer(hTask, 0);
|
||||
assert(thread != NULL);
|
||||
return thread->is_service;
|
||||
return thread->is_static;
|
||||
}
|
||||
|
||||
void furi_thread_set_stack_size(FuriThread* thread, size_t stack_size) {
|
||||
@@ -281,7 +281,7 @@ void furi_thread_start(FuriThread* thread) {
|
||||
|
||||
uint32_t stack = thread->stack_size / sizeof(StackType_t);
|
||||
UBaseType_t priority = thread->priority ? thread->priority : FuriThreadPriorityNormal;
|
||||
if (thread->is_service) {
|
||||
if (thread->is_static) {
|
||||
thread->task_handle = xTaskCreateStatic(
|
||||
furi_thread_body,
|
||||
thread->name,
|
||||
|
||||
@@ -109,7 +109,7 @@ void furi_thread_set_appid(FuriThread* thread, const char* appid);
|
||||
*
|
||||
* @param thread
|
||||
*/
|
||||
void furi_thread_mark_as_service(FuriThread* thread);
|
||||
void furi_thread_mark_as_static(FuriThread* thread);
|
||||
|
||||
/** Set FuriThread stack size
|
||||
*
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "desktop.h"
|
||||
#include "lvgl.h"
|
||||
#include "check.h"
|
||||
#include "record.h"
|
||||
#include "apps/services/loader/loader.h"
|
||||
#include "apps/services/gui/gui.h"
|
||||
#include "apps/services/gui/view_port.h"
|
||||
@@ -11,9 +10,7 @@ static void on_open_app(lv_event_t* e) {
|
||||
lv_event_code_t code = lv_event_get_code(e);
|
||||
if (code == LV_EVENT_CLICKED) {
|
||||
const AppManifest* manifest = lv_event_get_user_data(e);
|
||||
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
|
||||
loader_start_app_nonblocking(loader, manifest->id, NULL);
|
||||
})
|
||||
loader_start_app_nonblocking(manifest->id, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,9 +35,7 @@ static void desktop_show(lv_obj_t* parent, void* context) {
|
||||
static void desktop_start() {
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, &desktop_show, NULL);
|
||||
FURI_RECORD_TRANSACTION(RECORD_GUI, Gui*, gui, {
|
||||
gui_add_view_port(gui, view_port, GuiLayerDesktop);
|
||||
})
|
||||
gui_add_view_port(view_port, GuiLayerDesktop);
|
||||
}
|
||||
|
||||
static void desktop_stop() {
|
||||
@@ -54,6 +49,5 @@ const AppManifest desktop_app = {
|
||||
.type = AppTypeDesktop,
|
||||
.on_start = &desktop_start,
|
||||
.on_stop = &desktop_stop,
|
||||
.on_show = NULL,
|
||||
.stack_size = AppStackSizeNormal
|
||||
.on_show = NULL
|
||||
};
|
||||
|
||||
@@ -4,192 +4,36 @@
|
||||
#include "gui_i.h"
|
||||
#include "log.h"
|
||||
#include "record.h"
|
||||
#include "kernel.h"
|
||||
|
||||
#define TAG "gui"
|
||||
|
||||
// Forward declarations from gui_draw.c
|
||||
// Forward declarations
|
||||
bool gui_redraw_fs(Gui*);
|
||||
void gui_redraw(Gui*);
|
||||
static int32_t gui_main(void*);
|
||||
|
||||
ViewPort* gui_view_port_find_enabled(ViewPortArray_t array) {
|
||||
// Iterating backward
|
||||
ViewPortArray_it_t it;
|
||||
ViewPortArray_it_last(it, array);
|
||||
while (!ViewPortArray_end_p(it)) {
|
||||
ViewPort* view_port = *ViewPortArray_ref(it);
|
||||
if (view_port_is_enabled(view_port)) {
|
||||
ViewPort* view_port = *ViewPortArray_ref(it);
|
||||
return view_port;
|
||||
}
|
||||
ViewPortArray_previous(it);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t gui_active_view_port_count(Gui* gui, GuiLayer layer) {
|
||||
furi_assert(gui);
|
||||
furi_check(layer < GuiLayerMAX);
|
||||
size_t ret = 0;
|
||||
|
||||
gui_lock(gui);
|
||||
ViewPortArray_it_t it;
|
||||
ViewPortArray_it_last(it, gui->layers[layer]);
|
||||
while (!ViewPortArray_end_p(it)) {
|
||||
ViewPort* view_port = *ViewPortArray_ref(it);
|
||||
if (view_port_is_enabled(view_port)) {
|
||||
ret++;
|
||||
}
|
||||
ViewPortArray_previous(it);
|
||||
}
|
||||
gui_unlock(gui);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void gui_update(Gui* gui) {
|
||||
furi_assert(gui);
|
||||
|
||||
FuriThreadId thread_id = furi_thread_get_id(gui->thread);
|
||||
furi_thread_flags_set(thread_id, GUI_THREAD_FLAG_DRAW);
|
||||
}
|
||||
|
||||
void gui_lock(Gui* gui) {
|
||||
furi_assert(gui);
|
||||
furi_check(furi_mutex_acquire(gui->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void gui_unlock(Gui* gui) {
|
||||
furi_assert(gui);
|
||||
furi_check(furi_mutex_release(gui->mutex) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void gui_add_view_port(Gui* gui, ViewPort* view_port, GuiLayer layer) {
|
||||
furi_assert(gui);
|
||||
furi_assert(view_port);
|
||||
furi_check(layer < GuiLayerMAX);
|
||||
|
||||
gui_lock(gui);
|
||||
// Verify that view port is not yet added
|
||||
ViewPortArray_it_t it;
|
||||
for (size_t i = 0; i < GuiLayerMAX; i++) {
|
||||
ViewPortArray_it(it, gui->layers[i]);
|
||||
while (!ViewPortArray_end_p(it)) {
|
||||
furi_assert(*ViewPortArray_ref(it) != view_port);
|
||||
ViewPortArray_next(it);
|
||||
}
|
||||
}
|
||||
// Add view port and link with gui
|
||||
ViewPortArray_push_back(gui->layers[layer], view_port);
|
||||
view_port_gui_set(view_port, gui);
|
||||
gui_unlock(gui);
|
||||
|
||||
// Request redraw
|
||||
gui_update(gui);
|
||||
}
|
||||
|
||||
void gui_remove_view_port(Gui* gui, ViewPort* view_port) {
|
||||
furi_assert(gui);
|
||||
furi_assert(view_port);
|
||||
|
||||
gui_lock(gui);
|
||||
view_port_gui_set(view_port, NULL);
|
||||
ViewPortArray_it_t it;
|
||||
for (size_t i = 0; i < GuiLayerMAX; i++) {
|
||||
ViewPortArray_it(it, gui->layers[i]);
|
||||
while (!ViewPortArray_end_p(it)) {
|
||||
if (*ViewPortArray_ref(it) == view_port) {
|
||||
ViewPortArray_remove(gui->layers[i], it);
|
||||
} else {
|
||||
ViewPortArray_next(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
if(gui->ongoing_input_view_port == view_port) {
|
||||
gui->ongoing_input_view_port = NULL;
|
||||
}
|
||||
*/
|
||||
gui_unlock(gui);
|
||||
|
||||
// Request redraw
|
||||
gui_update(gui);
|
||||
}
|
||||
|
||||
void gui_view_port_send_to_front(Gui* gui, ViewPort* view_port) {
|
||||
furi_assert(gui);
|
||||
furi_assert(view_port);
|
||||
|
||||
gui_lock(gui);
|
||||
// Remove
|
||||
GuiLayer layer = GuiLayerMAX;
|
||||
ViewPortArray_it_t it;
|
||||
for (size_t i = 0; i < GuiLayerMAX; i++) {
|
||||
ViewPortArray_it(it, gui->layers[i]);
|
||||
while (!ViewPortArray_end_p(it)) {
|
||||
if (*ViewPortArray_ref(it) == view_port) {
|
||||
ViewPortArray_remove(gui->layers[i], it);
|
||||
furi_assert(layer == GuiLayerMAX);
|
||||
layer = i;
|
||||
} else {
|
||||
ViewPortArray_next(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
furi_assert(layer != GuiLayerMAX);
|
||||
// Return to the top
|
||||
ViewPortArray_push_back(gui->layers[layer], view_port);
|
||||
gui_unlock(gui);
|
||||
|
||||
// Request redraw
|
||||
gui_update(gui);
|
||||
}
|
||||
|
||||
void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port) {
|
||||
furi_assert(gui);
|
||||
furi_assert(view_port);
|
||||
|
||||
gui_lock(gui);
|
||||
// Remove
|
||||
GuiLayer layer = GuiLayerMAX;
|
||||
ViewPortArray_it_t it;
|
||||
for (size_t i = 0; i < GuiLayerMAX; i++) {
|
||||
ViewPortArray_it(it, gui->layers[i]);
|
||||
while (!ViewPortArray_end_p(it)) {
|
||||
if (*ViewPortArray_ref(it) == view_port) {
|
||||
ViewPortArray_remove(gui->layers[i], it);
|
||||
furi_assert(layer == GuiLayerMAX);
|
||||
layer = i;
|
||||
} else {
|
||||
ViewPortArray_next(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
furi_assert(layer != GuiLayerMAX);
|
||||
// Return to the top
|
||||
ViewPortArray_push_at(gui->layers[layer], 0, view_port);
|
||||
gui_unlock(gui);
|
||||
|
||||
// Request redraw
|
||||
gui_update(gui);
|
||||
}
|
||||
static Gui* gui = NULL;
|
||||
|
||||
Gui* gui_alloc() {
|
||||
Gui* gui = malloc(sizeof(Gui));
|
||||
gui->thread = furi_thread_alloc_ex(
|
||||
Gui* instance = malloc(sizeof(Gui));
|
||||
memset(instance, 0, sizeof(Gui));
|
||||
furi_check(instance != NULL);
|
||||
instance->thread = furi_thread_alloc_ex(
|
||||
"gui",
|
||||
2048,
|
||||
AppStackSizeLarge, // Last known minimum was 2800 for launching desktop
|
||||
&gui_main,
|
||||
NULL
|
||||
);
|
||||
gui->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
|
||||
instance->mutex = xSemaphoreCreateRecursiveMutex();
|
||||
|
||||
furi_check(lvgl_port_lock(100));
|
||||
gui->lvgl_parent = lv_scr_act();
|
||||
instance->lvgl_parent = lv_scr_act();
|
||||
lvgl_port_unlock();
|
||||
|
||||
for (size_t i = 0; i < GuiLayerMAX; i++) {
|
||||
ViewPortArray_init(gui->layers[i]);
|
||||
instance->layers[i] = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -200,25 +44,72 @@ Gui* gui_alloc() {
|
||||
furi_check(gui->input_events);
|
||||
furi_pubsub_subscribe(gui->input_events, gui_input_events_callback, gui);
|
||||
*/
|
||||
return gui;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void gui_free(Gui* gui) {
|
||||
furi_thread_free(gui->thread);
|
||||
void gui_free(Gui* instance) {
|
||||
furi_assert(instance != NULL);
|
||||
furi_thread_free(instance->thread);
|
||||
furi_mutex_free(instance->mutex);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
if (gui->mutex) {
|
||||
furi_mutex_free(gui->mutex);
|
||||
}
|
||||
void gui_lock() {
|
||||
furi_assert(gui);
|
||||
furi_assert(gui->mutex);
|
||||
furi_check(xSemaphoreTakeRecursive(gui->mutex, portMAX_DELAY) == pdPASS);
|
||||
}
|
||||
|
||||
void gui_unlock() {
|
||||
furi_assert(gui);
|
||||
furi_assert(gui->mutex);
|
||||
furi_check(xSemaphoreGiveRecursive(gui->mutex) == pdPASS);
|
||||
}
|
||||
|
||||
void gui_request_draw() {
|
||||
furi_assert(gui);
|
||||
|
||||
FuriThreadId thread_id = furi_thread_get_id(gui->thread);
|
||||
furi_thread_flags_set(thread_id, GUI_THREAD_FLAG_DRAW);
|
||||
}
|
||||
|
||||
void gui_add_view_port(ViewPort* view_port, GuiLayer layer) {
|
||||
furi_assert(gui);
|
||||
furi_assert(view_port);
|
||||
furi_check(layer < GuiLayerMAX);
|
||||
|
||||
gui_lock();
|
||||
furi_check(gui->layers[layer] == NULL, "layer in use");
|
||||
gui->layers[layer] = view_port;
|
||||
view_port_gui_set(view_port, gui);
|
||||
gui_unlock();
|
||||
|
||||
gui_request_draw();
|
||||
}
|
||||
|
||||
void gui_remove_view_port(ViewPort* view_port) {
|
||||
furi_assert(gui);
|
||||
furi_assert(view_port);
|
||||
|
||||
gui_lock();
|
||||
|
||||
view_port_gui_set(view_port, NULL);
|
||||
for (size_t i = 0; i < GuiLayerMAX; i++) {
|
||||
ViewPortArray_clear(gui->layers[i]);
|
||||
if (gui->layers[i] == view_port) {
|
||||
gui->layers[i] = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(gui);
|
||||
gui_unlock();
|
||||
|
||||
gui_request_draw();
|
||||
}
|
||||
|
||||
static int32_t gui_main(void* parameter) {
|
||||
UNUSED(parameter);
|
||||
static int32_t gui_main(void* p) {
|
||||
UNUSED(p);
|
||||
furi_check(gui);
|
||||
Gui* local_gui = gui;
|
||||
|
||||
while (1) {
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
@@ -236,11 +127,10 @@ static int32_t gui_main(void* parameter) {
|
||||
}*/
|
||||
// Process and dispatch draw call
|
||||
if (flags & GUI_THREAD_FLAG_DRAW) {
|
||||
FURI_LOG_D(TAG, "redraw requested");
|
||||
furi_thread_flags_clear(GUI_THREAD_FLAG_DRAW);
|
||||
FURI_RECORD_TRANSACTION(RECORD_GUI, Gui*, gui, {
|
||||
gui_redraw(gui);
|
||||
})
|
||||
gui_lock();
|
||||
gui_redraw(local_gui);
|
||||
gui_unlock();
|
||||
}
|
||||
|
||||
if (flags & GUI_THREAD_FLAG_EXIT) {
|
||||
@@ -252,29 +142,27 @@ static int32_t gui_main(void* parameter) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// region AppManifest
|
||||
|
||||
static void gui_start(void* parameter) {
|
||||
UNUSED(parameter);
|
||||
|
||||
Gui* gui = gui_alloc();
|
||||
furi_record_create(RECORD_GUI, gui);
|
||||
furi_thread_set_priority(gui->thread, FuriThreadPriorityHigh);
|
||||
gui = gui_alloc();
|
||||
|
||||
furi_thread_set_priority(gui->thread, FuriThreadPriorityNormal);
|
||||
furi_thread_start(gui->thread);
|
||||
}
|
||||
|
||||
static void gui_stop() {
|
||||
FURI_RECORD_TRANSACTION(RECORD_GUI, Gui*, gui, {
|
||||
gui_lock(gui);
|
||||
gui_lock();
|
||||
|
||||
FuriThreadId thread_id = furi_thread_get_id(gui->thread);
|
||||
furi_thread_flags_set(thread_id, GUI_THREAD_FLAG_EXIT);
|
||||
furi_thread_join(gui->thread);
|
||||
FuriThreadId thread_id = furi_thread_get_id(gui->thread);
|
||||
furi_thread_flags_set(thread_id, GUI_THREAD_FLAG_EXIT);
|
||||
furi_thread_join(gui->thread);
|
||||
|
||||
gui_unlock(gui);
|
||||
gui_unlock();
|
||||
|
||||
gui_free(gui);
|
||||
})
|
||||
|
||||
furi_record_destroy(RECORD_GUI);
|
||||
gui_free(gui);
|
||||
}
|
||||
|
||||
const AppManifest gui_app = {
|
||||
@@ -284,6 +172,7 @@ const AppManifest gui_app = {
|
||||
.type = AppTypeService,
|
||||
.on_start = &gui_start,
|
||||
.on_stop = &gui_stop,
|
||||
.on_show = NULL,
|
||||
.stack_size = AppStackSizeNormal
|
||||
.on_show = NULL
|
||||
};
|
||||
|
||||
// endregion
|
||||
|
||||
@@ -12,26 +12,11 @@ extern const AppManifest gui_app;
|
||||
/** Gui layers */
|
||||
typedef enum {
|
||||
GuiLayerDesktop, /**< Desktop layer for internal use. Like fullscreen but with status bar */
|
||||
|
||||
GuiLayerWindow, /**< Window layer, status bar is shown */
|
||||
|
||||
GuiLayerStatusBarLeft, /**< Status bar left-side layer, auto-layout */
|
||||
GuiLayerStatusBarRight, /**< Status bar right-side layer, auto-layout */
|
||||
|
||||
GuiLayerFullscreen, /**< Fullscreen layer, no status bar */
|
||||
|
||||
GuiLayerMAX /**< Don't use or move, special value */
|
||||
} GuiLayer;
|
||||
|
||||
/** Gui Canvas Commit Callback */
|
||||
typedef void (*GuiCanvasCommitCallback)(
|
||||
uint8_t* data,
|
||||
size_t size,
|
||||
void* context
|
||||
);
|
||||
|
||||
#define RECORD_GUI "gui"
|
||||
|
||||
typedef struct Gui Gui;
|
||||
|
||||
/** Add view_port to view_port tree
|
||||
@@ -42,7 +27,7 @@ typedef struct Gui Gui;
|
||||
* @param view_port ViewPort instance
|
||||
* @param[in] layer GuiLayer where to place view_port
|
||||
*/
|
||||
void gui_add_view_port(Gui* gui, ViewPort* view_port, GuiLayer layer);
|
||||
void gui_add_view_port(ViewPort* view_port, GuiLayer layer);
|
||||
|
||||
/** Remove view_port from rendering tree
|
||||
*
|
||||
@@ -51,25 +36,7 @@ void gui_add_view_port(Gui* gui, ViewPort* view_port, GuiLayer layer);
|
||||
* @param gui Gui instance
|
||||
* @param view_port ViewPort instance
|
||||
*/
|
||||
void gui_remove_view_port(Gui* gui, ViewPort* view_port);
|
||||
|
||||
/** Send ViewPort to the front
|
||||
*
|
||||
* Places selected ViewPort to the top of the drawing stack
|
||||
*
|
||||
* @param gui Gui instance
|
||||
* @param view_port ViewPort instance
|
||||
*/
|
||||
void gui_view_port_send_to_front(Gui* gui, ViewPort* view_port);
|
||||
|
||||
/** Send ViewPort to the back
|
||||
*
|
||||
* Places selected ViewPort to the bottom of the drawing stack
|
||||
*
|
||||
* @param gui Gui instance
|
||||
* @param view_port ViewPort instance
|
||||
*/
|
||||
void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port);
|
||||
void gui_remove_view_port(ViewPort* view_port);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -39,12 +39,10 @@ static lv_obj_t* screen_with_top_bar_and_toolbar(lv_obj_t* parent) {
|
||||
|
||||
top_bar(vertical_container);
|
||||
|
||||
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
|
||||
const AppManifest* manifest = loader_get_current_app(loader);
|
||||
if (manifest != NULL) {
|
||||
toolbar(vertical_container, TOP_BAR_HEIGHT, manifest);
|
||||
}
|
||||
})
|
||||
const AppManifest* manifest = loader_get_current_app();
|
||||
if (manifest != NULL) {
|
||||
toolbar(vertical_container, TOP_BAR_HEIGHT, manifest);
|
||||
}
|
||||
|
||||
lv_obj_t* spacer = lv_obj_create(vertical_container);
|
||||
lv_obj_set_size(spacer, 2, 2);
|
||||
@@ -60,7 +58,7 @@ static lv_obj_t* screen_with_top_bar_and_toolbar(lv_obj_t* parent) {
|
||||
}
|
||||
|
||||
static bool gui_redraw_window(Gui* gui) {
|
||||
ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerWindow]);
|
||||
ViewPort* view_port = gui->layers[GuiLayerWindow];
|
||||
if (view_port) {
|
||||
lv_obj_t* container = screen_with_top_bar_and_toolbar(gui->lvgl_parent);
|
||||
view_port_draw(view_port, container);
|
||||
@@ -71,7 +69,7 @@ static bool gui_redraw_window(Gui* gui) {
|
||||
}
|
||||
|
||||
static bool gui_redraw_desktop(Gui* gui) {
|
||||
ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]);
|
||||
ViewPort* view_port = gui->layers[GuiLayerDesktop];
|
||||
if (view_port) {
|
||||
lv_obj_t* container = screen_with_top_bar(gui->lvgl_parent);
|
||||
view_port_draw(view_port, container);
|
||||
@@ -84,7 +82,7 @@ static bool gui_redraw_desktop(Gui* gui) {
|
||||
}
|
||||
|
||||
bool gui_redraw_fs(Gui* gui) {
|
||||
ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]);
|
||||
ViewPort* view_port = gui->layers[GuiLayerFullscreen];
|
||||
if (view_port) {
|
||||
view_port_draw(view_port, gui->lvgl_parent);
|
||||
return true;
|
||||
@@ -95,7 +93,6 @@ bool gui_redraw_fs(Gui* gui) {
|
||||
|
||||
void gui_redraw(Gui* gui) {
|
||||
furi_assert(gui);
|
||||
gui_lock(gui);
|
||||
|
||||
furi_check(lvgl_port_lock(100));
|
||||
lv_obj_clean(gui->lvgl_parent);
|
||||
@@ -107,6 +104,4 @@ void gui_redraw(Gui* gui) {
|
||||
}
|
||||
|
||||
lvgl_port_unlock();
|
||||
|
||||
gui_unlock(gui);
|
||||
}
|
||||
|
||||
@@ -1,57 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "gui.h"
|
||||
|
||||
#include <m-algo.h>
|
||||
#include <m-array.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "message_queue.h"
|
||||
#include "mutex.h"
|
||||
#include "pubsub.h"
|
||||
#include "view_port.h"
|
||||
#include "view_port_i.h"
|
||||
|
||||
#define GUI_DISPLAY_WIDTH 128
|
||||
#define GUI_DISPLAY_HEIGHT 64
|
||||
|
||||
#define GUI_STATUS_BAR_X 0
|
||||
#define GUI_STATUS_BAR_Y 0
|
||||
#define GUI_STATUS_BAR_WIDTH GUI_DISPLAY_WIDTH
|
||||
/* 0-1 pixels for upper thin frame
|
||||
* 2-9 pixels for icons (battery, sd card, etc)
|
||||
* 10-12 pixels for lower bold line */
|
||||
#define GUI_STATUS_BAR_HEIGHT 13
|
||||
/* icon itself area (battery, sd card, etc) excluding frame.
|
||||
* painted 2 pixels below GUI_STATUS_BAR_X.
|
||||
*/
|
||||
#define GUI_STATUS_BAR_WORKAREA_HEIGHT 8
|
||||
|
||||
#define GUI_WINDOW_X 0
|
||||
#define GUI_WINDOW_Y GUI_STATUS_BAR_HEIGHT
|
||||
#define GUI_WINDOW_WIDTH GUI_DISPLAY_WIDTH
|
||||
#define GUI_WINDOW_HEIGHT (GUI_DISPLAY_HEIGHT - GUI_WINDOW_Y)
|
||||
#include <stdio.h>
|
||||
|
||||
#define GUI_THREAD_FLAG_DRAW (1 << 0)
|
||||
#define GUI_THREAD_FLAG_INPUT (1 << 1)
|
||||
#define GUI_THREAD_FLAG_EXIT (1 << 2)
|
||||
#define GUI_THREAD_FLAG_ALL (GUI_THREAD_FLAG_DRAW | GUI_THREAD_FLAG_INPUT | GUI_THREAD_FLAG_EXIT)
|
||||
|
||||
ARRAY_DEF(ViewPortArray, ViewPort*, M_PTR_OPLIST);
|
||||
|
||||
typedef struct {
|
||||
GuiCanvasCommitCallback callback;
|
||||
void* context;
|
||||
} CanvasCallbackPair;
|
||||
|
||||
/** Gui structure */
|
||||
struct Gui {
|
||||
// Thread and lock
|
||||
FuriThread* thread;
|
||||
FuriMutex* mutex;
|
||||
SemaphoreHandle_t mutex;
|
||||
|
||||
// Layers and Canvas
|
||||
ViewPortArray_t layers[GuiLayerMAX];
|
||||
ViewPort* layers[GuiLayerMAX];
|
||||
lv_obj_t* lvgl_parent;
|
||||
|
||||
// Input
|
||||
@@ -63,19 +34,11 @@ struct Gui {
|
||||
*/
|
||||
};
|
||||
|
||||
/** Find enabled ViewPort in ViewPortArray
|
||||
*
|
||||
* @param[in] array The ViewPortArray instance
|
||||
*
|
||||
* @return ViewPort instance or NULL
|
||||
*/
|
||||
ViewPort* gui_view_port_find_enabled(ViewPortArray_t array);
|
||||
|
||||
/** Update GUI, request redraw
|
||||
*
|
||||
* @param gui Gui instance
|
||||
*/
|
||||
void gui_update(Gui* gui);
|
||||
void gui_request_draw();
|
||||
|
||||
///** Input event callback
|
||||
// *
|
||||
@@ -86,21 +49,14 @@ void gui_update(Gui* gui);
|
||||
// */
|
||||
//void gui_input_events_callback(const void* value, void* ctx);
|
||||
|
||||
/** Get count of view ports in layer
|
||||
*
|
||||
* @param gui The Gui instance
|
||||
* @param[in] layer GuiLayer that we want to get count of view ports
|
||||
*/
|
||||
size_t gui_active_view_port_count(Gui* gui, GuiLayer layer);
|
||||
|
||||
/** Lock GUI
|
||||
*
|
||||
* @param gui The Gui instance
|
||||
*/
|
||||
void gui_lock(Gui* gui);
|
||||
void gui_lock();
|
||||
|
||||
/** Unlock GUI
|
||||
*
|
||||
* @param gui The Gui instance
|
||||
*/
|
||||
void gui_unlock(Gui* gui);
|
||||
void gui_unlock();
|
||||
|
||||
@@ -36,7 +36,7 @@ void view_port_enabled_set(ViewPort* view_port, bool enabled) {
|
||||
furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
if (view_port->is_enabled != enabled) {
|
||||
view_port->is_enabled = enabled;
|
||||
if (view_port->gui) gui_update(view_port->gui);
|
||||
if (view_port->gui) gui_request_draw();
|
||||
}
|
||||
furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
|
||||
}
|
||||
@@ -66,7 +66,7 @@ void view_port_update(ViewPort* view_port) {
|
||||
ESP_LOGW(TAG, "ViewPort lockup: see %s:%d", __FILE__, __LINE__ - 3);
|
||||
}
|
||||
|
||||
if (view_port->gui && view_port->is_enabled) gui_update(view_port->gui);
|
||||
if (view_port->gui && view_port->is_enabled) gui_request_draw();
|
||||
furi_mutex_release(view_port->mutex);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
#include "apps/services/loader/loader.h"
|
||||
|
||||
static void app_toolbar_close(lv_event_t* event) {
|
||||
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
|
||||
loader_stop_app(loader);
|
||||
})
|
||||
loader_stop_app();
|
||||
}
|
||||
|
||||
void toolbar(lv_obj_t* parent, lv_coord_t offset_y, const AppManifest* manifest) {
|
||||
|
||||
@@ -6,18 +6,55 @@
|
||||
#include <sys/cdefs.h>
|
||||
#include "esp_heap_caps.h"
|
||||
#include "apps/services/gui/gui.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
#define TAG "Loader"
|
||||
|
||||
// Forward declarations
|
||||
static int32_t loader_main(void* p);
|
||||
static LoaderStatus loader_do_start_by_id(
|
||||
Loader* loader,
|
||||
const char* id,
|
||||
const char* args,
|
||||
FuriString* error_message
|
||||
);
|
||||
|
||||
LoaderStatus loader_start_app(Loader* loader, const char* id, const char* args, FuriString* error_message) {
|
||||
static Loader* loader = NULL;
|
||||
|
||||
static Loader* loader_alloc() {
|
||||
furi_check(loader == NULL);
|
||||
loader = malloc(sizeof(Loader));
|
||||
loader->pubsub = furi_pubsub_alloc();
|
||||
loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage));
|
||||
loader->thread = furi_thread_alloc_ex(
|
||||
"loader",
|
||||
AppStackSizeLarge, // Last known minimum was 2400 for starting Hello World app
|
||||
&loader_main,
|
||||
NULL
|
||||
);
|
||||
loader->app_data.args = NULL;
|
||||
loader->app_data.app = NULL;
|
||||
loader->app_data.view_port = NULL;
|
||||
loader->mutex = xSemaphoreCreateRecursiveMutex();
|
||||
return loader;
|
||||
}
|
||||
|
||||
static void loader_free() {
|
||||
furi_check(loader != NULL);
|
||||
furi_thread_free(loader->thread);
|
||||
furi_pubsub_free(loader->pubsub);
|
||||
furi_message_queue_free(loader->queue);
|
||||
furi_mutex_free(loader->mutex);
|
||||
free(loader);
|
||||
}
|
||||
|
||||
void loader_lock() {
|
||||
furi_assert(loader);
|
||||
furi_assert(loader->mutex);
|
||||
furi_check(xSemaphoreTakeRecursive(loader->mutex, portMAX_DELAY) == pdPASS);
|
||||
}
|
||||
|
||||
void loader_unlock() {
|
||||
furi_assert(loader);
|
||||
furi_assert(loader->mutex);
|
||||
furi_check(xSemaphoreGiveRecursive(loader->mutex) == pdPASS);
|
||||
}
|
||||
|
||||
LoaderStatus loader_start_app(const char* id, const char* args, FuriString* error_message) {
|
||||
LoaderMessage message;
|
||||
LoaderMessageLoaderStatusResult result;
|
||||
|
||||
@@ -32,7 +69,7 @@ LoaderStatus loader_start_app(Loader* loader, const char* id, const char* args,
|
||||
return result.value;
|
||||
}
|
||||
|
||||
void loader_start_app_nonblocking(Loader* loader, const char* id, const char* args) {
|
||||
void loader_start_app_nonblocking(const char* id, const char* args) {
|
||||
LoaderMessage message;
|
||||
LoaderMessageLoaderStatusResult result;
|
||||
|
||||
@@ -45,22 +82,22 @@ void loader_start_app_nonblocking(Loader* loader, const char* id, const char* ar
|
||||
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
|
||||
}
|
||||
|
||||
void loader_stop_app(Loader* loader) {
|
||||
void loader_stop_app() {
|
||||
LoaderMessage message;
|
||||
message.type = LoaderMessageTypeAppStop;
|
||||
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
|
||||
}
|
||||
|
||||
const AppManifest* _Nullable loader_get_current_app(Loader* loader) {
|
||||
App* app = loader->app_data.app;
|
||||
if (app != NULL) {
|
||||
return app->manifest;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
const AppManifest* _Nullable loader_get_current_app() {
|
||||
loader_lock();
|
||||
const App* app = loader->app_data.app;
|
||||
const AppManifest* manifest = app ? app->manifest : NULL;
|
||||
loader_unlock();
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
||||
FuriPubSub* loader_get_pubsub(Loader* loader) {
|
||||
FuriPubSub* loader_get_pubsub() {
|
||||
furi_assert(loader);
|
||||
// it's safe to return pubsub without locking
|
||||
// because it's never freed and loader is never exited
|
||||
@@ -68,30 +105,6 @@ FuriPubSub* loader_get_pubsub(Loader* loader) {
|
||||
return loader->pubsub;
|
||||
}
|
||||
|
||||
// implementation
|
||||
|
||||
static Loader* loader_alloc() {
|
||||
Loader* loader = malloc(sizeof(Loader));
|
||||
loader->pubsub = furi_pubsub_alloc();
|
||||
loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage));
|
||||
loader->thread = furi_thread_alloc_ex(
|
||||
"loader",
|
||||
2048,
|
||||
&loader_main,
|
||||
NULL
|
||||
);
|
||||
loader->app_data.args = NULL;
|
||||
loader->app_data.app = NULL;
|
||||
return loader;
|
||||
}
|
||||
|
||||
static void loader_free(Loader* loader) {
|
||||
furi_pubsub_free(loader->pubsub);
|
||||
furi_message_queue_free(loader->queue);
|
||||
furi_thread_free(loader->thread);
|
||||
free(loader);
|
||||
}
|
||||
|
||||
static void loader_log_status_error(
|
||||
LoaderStatus status,
|
||||
FuriString* error_message,
|
||||
@@ -128,15 +141,19 @@ static LoaderStatus loader_make_success_status(FuriString* error_message) {
|
||||
}
|
||||
|
||||
static void loader_start_app_with_manifest(
|
||||
Loader* loader,
|
||||
const AppManifest* _Nonnull manifest,
|
||||
const char* args
|
||||
) {
|
||||
FURI_LOG_I(TAG, "start with manifest %s", manifest->id);
|
||||
|
||||
if (manifest->type != AppTypeUser && manifest->type != AppTypeSystem) {
|
||||
furi_crash("app type not supported by loader");
|
||||
furi_crash("App type not supported by loader");
|
||||
}
|
||||
|
||||
App* _Nonnull app = furi_app_alloc(manifest);
|
||||
|
||||
loader_lock();
|
||||
|
||||
loader->app_data.app = app;
|
||||
loader->app_data.args = (void*)args;
|
||||
|
||||
@@ -149,23 +166,20 @@ static void loader_start_app_with_manifest(
|
||||
loader->app_data.view_port = view_port;
|
||||
view_port_draw_callback_set(view_port, manifest->on_show, NULL);
|
||||
|
||||
FURI_RECORD_TRANSACTION(RECORD_GUI, Gui*, gui, {
|
||||
gui_add_view_port(gui, view_port, GuiLayerWindow);
|
||||
})
|
||||
gui_add_view_port(view_port, GuiLayerWindow);
|
||||
} else {
|
||||
loader->app_data.view_port = NULL;
|
||||
}
|
||||
|
||||
loader_unlock();
|
||||
}
|
||||
|
||||
// process messages
|
||||
|
||||
static LoaderStatus loader_do_start_by_id(
|
||||
Loader* loader,
|
||||
const char* id,
|
||||
const char* args,
|
||||
FuriString* _Nullable error_message
|
||||
) {
|
||||
FURI_LOG_I(TAG, "loader start by id %s", id);
|
||||
FURI_LOG_I(TAG, "Start by id %s", id);
|
||||
|
||||
const AppManifest* manifest = app_manifest_registry_find_by_id(id);
|
||||
if (manifest == NULL) {
|
||||
@@ -177,11 +191,13 @@ static LoaderStatus loader_do_start_by_id(
|
||||
);
|
||||
}
|
||||
|
||||
loader_start_app_with_manifest(loader, manifest, args);
|
||||
loader_start_app_with_manifest(manifest, args);
|
||||
return loader_make_success_status(error_message);
|
||||
}
|
||||
|
||||
static void loader_do_stop_app(Loader* loader) {
|
||||
static void loader_do_stop_app() {
|
||||
loader_lock();
|
||||
|
||||
App* app = loader->app_data.app;
|
||||
if (app == NULL) {
|
||||
FURI_LOG_W(TAG, "Stop app: no app running");
|
||||
@@ -192,9 +208,7 @@ static void loader_do_stop_app(Loader* loader) {
|
||||
|
||||
ViewPort* view_port = loader->app_data.view_port;
|
||||
if (view_port) {
|
||||
FURI_RECORD_TRANSACTION(RECORD_GUI, Gui*, gui, {
|
||||
gui_remove_view_port(gui, view_port);
|
||||
})
|
||||
gui_remove_view_port(view_port);
|
||||
view_port_free(view_port);
|
||||
loader->app_data.view_port = NULL;
|
||||
}
|
||||
@@ -211,6 +225,8 @@ static void loader_do_stop_app(Loader* loader) {
|
||||
furi_app_free(loader->app_data.app);
|
||||
loader->app_data.app = NULL;
|
||||
|
||||
loader_unlock();
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Application stopped. Free heap: %zu",
|
||||
@@ -222,43 +238,38 @@ static void loader_do_stop_app(Loader* loader) {
|
||||
furi_pubsub_publish(loader->pubsub, &event);
|
||||
}
|
||||
|
||||
// app
|
||||
bool loader_is_app_running() {
|
||||
loader_lock();
|
||||
bool is_running = loader->app_data.app != NULL;
|
||||
loader_unlock();
|
||||
return is_running;
|
||||
}
|
||||
|
||||
static int32_t loader_main(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
FuriMessageQueue* queue = NULL;
|
||||
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
|
||||
queue = loader->queue;
|
||||
})
|
||||
furi_check(queue != NULL);
|
||||
|
||||
LoaderMessage message;
|
||||
bool exit_requested = false;
|
||||
while (!exit_requested) {
|
||||
if (furi_message_queue_get(queue, &message, FuriWaitForever) == FuriStatusOk) {
|
||||
FURI_LOG_I(TAG, "processing message of type %d", message.type);
|
||||
furi_check(loader != NULL);
|
||||
if (furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) {
|
||||
FURI_LOG_I(TAG, "Processing message of type %d", message.type);
|
||||
switch (message.type) {
|
||||
case LoaderMessageTypeStartByName:
|
||||
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
|
||||
if (loader->app_data.app) {
|
||||
loader_do_stop_app(loader);
|
||||
}
|
||||
message.status_value->value = loader_do_start_by_id(
|
||||
loader,
|
||||
message.start.id,
|
||||
message.start.args,
|
||||
message.start.error_message
|
||||
);
|
||||
if (message.api_lock) {
|
||||
api_lock_unlock(message.api_lock);
|
||||
}
|
||||
})
|
||||
if (loader_is_app_running()) {
|
||||
loader_do_stop_app();
|
||||
}
|
||||
message.status_value->value = loader_do_start_by_id(
|
||||
message.start.id,
|
||||
message.start.args,
|
||||
message.start.error_message
|
||||
);
|
||||
if (message.api_lock) {
|
||||
api_lock_unlock(message.api_lock);
|
||||
}
|
||||
break;
|
||||
case LoaderMessageTypeAppStop:
|
||||
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
|
||||
loader_do_stop_app(loader);
|
||||
})
|
||||
loader_do_stop_app();
|
||||
break;
|
||||
case LoaderMessageTypeExit:
|
||||
exit_requested = true;
|
||||
@@ -270,30 +281,30 @@ static int32_t loader_main(void* p) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void loader_start(void* p) {
|
||||
Loader* loader = loader_alloc();
|
||||
furi_record_create(RECORD_LOADER, loader);
|
||||
furi_thread_set_priority(loader->thread, FuriThreadPriorityHigh);
|
||||
// region AppManifest
|
||||
|
||||
static void loader_start(void* parameter) {
|
||||
UNUSED(parameter);
|
||||
furi_check(loader == NULL);
|
||||
loader = loader_alloc();
|
||||
|
||||
furi_thread_set_priority(loader->thread, FuriThreadPriorityNormal);
|
||||
furi_thread_start(loader->thread);
|
||||
}
|
||||
|
||||
static void loader_stop() {
|
||||
furi_check(loader != NULL);
|
||||
LoaderMessage message = {
|
||||
.api_lock = NULL,
|
||||
.type = LoaderMessageTypeExit
|
||||
};
|
||||
|
||||
FURI_RECORD_TRANSACTION(RECORD_LOADER, Loader*, loader, {
|
||||
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
|
||||
// Send stop signal to thread and wait for thread to finish
|
||||
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
|
||||
furi_thread_join(loader->thread);
|
||||
|
||||
furi_thread_join(loader->thread);
|
||||
furi_thread_free(loader->thread);
|
||||
loader->thread = NULL;
|
||||
|
||||
loader_free(loader);
|
||||
})
|
||||
|
||||
furi_record_destroy(RECORD_LOADER);
|
||||
loader_free();
|
||||
loader = NULL;
|
||||
}
|
||||
|
||||
const AppManifest loader_app = {
|
||||
@@ -303,6 +314,7 @@ const AppManifest loader_app = {
|
||||
.type = AppTypeService,
|
||||
.on_start = &loader_start,
|
||||
.on_stop = &loader_stop,
|
||||
.on_show = NULL,
|
||||
.stack_size = AppStackSizeNormal
|
||||
.on_show = NULL
|
||||
};
|
||||
|
||||
// endregion
|
||||
@@ -8,8 +8,6 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define RECORD_LOADER "loader"
|
||||
|
||||
typedef struct Loader Loader;
|
||||
|
||||
typedef enum {
|
||||
@@ -36,7 +34,7 @@ typedef struct {
|
||||
* @param[out] error_message detailed error message, can be NULL
|
||||
* @return LoaderStatus
|
||||
*/
|
||||
LoaderStatus loader_start_app(Loader* loader, const char* id, const char* args, FuriString* error_message);
|
||||
LoaderStatus loader_start_app(const char* id, const char* args, FuriString* error_message);
|
||||
|
||||
/**
|
||||
* @brief Close any running app, then start new one. Non-blocking.
|
||||
@@ -44,11 +42,13 @@ LoaderStatus loader_start_app(Loader* loader, const char* id, const char* args,
|
||||
* @param[in] id application name or id
|
||||
* @param[in] args application arguments
|
||||
*/
|
||||
void loader_start_app_nonblocking(Loader* loader, const char* id, const char* args);
|
||||
void loader_start_app_nonblocking(const char* id, const char* args);
|
||||
|
||||
void loader_stop_app(Loader* loader);
|
||||
void loader_stop_app();
|
||||
|
||||
const AppManifest* _Nullable loader_get_current_app(Loader* loader);
|
||||
bool loader_is_app_running();
|
||||
|
||||
const AppManifest* _Nullable loader_get_current_app();
|
||||
/**
|
||||
* @brief Start application with GUI error message
|
||||
* @param[in] instance loader instance
|
||||
@@ -56,20 +56,20 @@ const AppManifest* _Nullable loader_get_current_app(Loader* loader);
|
||||
* @param[in] args application arguments
|
||||
* @return LoaderStatus
|
||||
*/
|
||||
//LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args);
|
||||
//LoaderStatus loader_start_with_gui_error(const char* name, const char* args);
|
||||
|
||||
/**
|
||||
* @brief Show loader menu
|
||||
* @param[in] instance loader instance
|
||||
*/
|
||||
void loader_show_menu(Loader* instance);
|
||||
void loader_show_menu();
|
||||
|
||||
/**
|
||||
* @brief Get loader pubsub
|
||||
* @param[in] instance loader instance
|
||||
* @return FuriPubSub*
|
||||
*/
|
||||
FuriPubSub* loader_get_pubsub(Loader* instance);
|
||||
FuriPubSub* loader_get_pubsub();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#pragma once
|
||||
#include "api_lock.h"
|
||||
#include "app_manifest.h"
|
||||
#include "apps/services/gui/view_port.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "loader.h"
|
||||
#include "message_queue.h"
|
||||
#include "pubsub.h"
|
||||
#include "thread.h"
|
||||
#include "apps/services/gui/view_port.h"
|
||||
|
||||
typedef struct {
|
||||
char* args;
|
||||
@@ -18,6 +20,7 @@ struct Loader {
|
||||
FuriPubSub* pubsub;
|
||||
FuriMessageQueue* queue;
|
||||
LoaderAppData app_data;
|
||||
SemaphoreHandle_t mutex;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
|
||||
@@ -38,6 +38,5 @@ AppManifest system_info_app = {
|
||||
.type = AppTypeSystem,
|
||||
.on_start = NULL,
|
||||
.on_stop = NULL,
|
||||
.on_show = app_show,
|
||||
.stack_size = AppStackSizeNormal
|
||||
.on_show = app_show
|
||||
};
|
||||
|
||||
@@ -5,7 +5,12 @@
|
||||
|
||||
#define TAG "hardware"
|
||||
|
||||
Devices nb_devices_create(Config _Nonnull* config) {
|
||||
Hardware nb_hardware_init(const HardwareConfig _Nonnull* config) {
|
||||
if (config->bootstrap != NULL) {
|
||||
ESP_LOGI(TAG, "Bootstrapping");
|
||||
config->bootstrap();
|
||||
}
|
||||
|
||||
furi_check(config->display_driver != NULL, "no display driver configured");
|
||||
DisplayDriver display_driver = config->display_driver();
|
||||
ESP_LOGI(TAG, "display with driver %s", display_driver.name);
|
||||
@@ -21,7 +26,7 @@ Devices nb_devices_create(Config _Nonnull* config) {
|
||||
touch = NULL;
|
||||
}
|
||||
|
||||
return (Devices) {
|
||||
return (Hardware) {
|
||||
.display = display,
|
||||
.touch = touch
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ extern "C" {
|
||||
typedef struct {
|
||||
DisplayDevice* _Nonnull display;
|
||||
TouchDevice* _Nullable touch;
|
||||
} Devices;
|
||||
} Hardware;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
Devices nb_devices_create(Config _Nonnull* config);
|
||||
Hardware nb_hardware_init(const HardwareConfig _Nonnull* config);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ typedef struct {
|
||||
uint16_t vertical_resolution;
|
||||
uint16_t draw_buffer_height;
|
||||
uint16_t bits_per_pixel;
|
||||
bool double_buffering;
|
||||
bool mirror_x;
|
||||
bool mirror_y;
|
||||
bool swap_xy;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#define TAG "lvgl"
|
||||
|
||||
Lvgl nb_graphics_init(Devices _Nonnull* hardware) {
|
||||
Lvgl nb_graphics_init(Hardware _Nonnull* hardware) {
|
||||
const lvgl_port_cfg_t lvgl_cfg = {
|
||||
.task_priority = 4,
|
||||
.task_stack = 4096,
|
||||
@@ -23,7 +23,7 @@ Lvgl nb_graphics_init(Devices _Nonnull* hardware) {
|
||||
.io_handle = display->io_handle,
|
||||
.panel_handle = display->display_handle,
|
||||
.buffer_size = display->horizontal_resolution * display->draw_buffer_height * (display->bits_per_pixel / 8),
|
||||
.double_buffer = 0,
|
||||
.double_buffer = display->double_buffering,
|
||||
.hres = display->horizontal_resolution,
|
||||
.vres = display->vertical_resolution,
|
||||
.monochrome = display->monochrome,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
Lvgl nb_graphics_init(Devices _Nonnull* hardware);
|
||||
Lvgl nb_graphics_init(Hardware _Nonnull* hardware);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
#include "furi.h"
|
||||
#include "graphics_i.h"
|
||||
#include "partitions.h"
|
||||
|
||||
#include "apps/services/gui/gui.h"
|
||||
#define TAG "nanobake"
|
||||
|
||||
Gui* gui_alloc();
|
||||
|
||||
// System services
|
||||
extern const AppManifest gui_app;
|
||||
extern const AppManifest loader_app;
|
||||
@@ -50,12 +52,12 @@ static void start_desktop() {
|
||||
FURI_LOG_I(TAG, "Startup complete");
|
||||
}
|
||||
|
||||
__attribute__((unused)) extern void nanobake_start(Config* _Nonnull config) {
|
||||
__attribute__((unused)) extern void nanobake_start(const Config* _Nonnull config) {
|
||||
furi_init();
|
||||
|
||||
nb_partitions_init();
|
||||
|
||||
Devices hardware = nb_devices_create(config);
|
||||
Hardware hardware = nb_hardware_init(config->hardware);
|
||||
/*NbLvgl lvgl =*/nb_graphics_init(&hardware);
|
||||
|
||||
register_apps(config);
|
||||
|
||||
@@ -10,20 +10,27 @@ extern "C" {
|
||||
|
||||
// Forward declarations
|
||||
typedef void* FuriThreadId;
|
||||
typedef void (*Bootstrap)();
|
||||
typedef TouchDriver (*CreateTouchDriver)();
|
||||
typedef DisplayDriver (*CreateDisplayDriver)();
|
||||
|
||||
typedef struct {
|
||||
// Optional bootstrapping method
|
||||
const Bootstrap _Nullable bootstrap;
|
||||
// Required driver for display
|
||||
const CreateDisplayDriver _Nonnull display_driver;
|
||||
// Optional driver for touch input
|
||||
const CreateTouchDriver _Nullable touch_driver;
|
||||
} HardwareConfig;
|
||||
|
||||
typedef struct {
|
||||
const HardwareConfig* hardware;
|
||||
// List of user applications
|
||||
const size_t apps_count;
|
||||
const AppManifest* const apps[];
|
||||
} Config;
|
||||
|
||||
__attribute__((unused)) extern void nanobake_start(Config _Nonnull* config);
|
||||
__attribute__((unused)) extern void nanobake_start(const Config _Nonnull* config);
|
||||
|
||||
FuriThreadId nanobake_get_app_thread_id(size_t index);
|
||||
size_t nanobake_get_app_thread_count();
|
||||
|
||||
Reference in New Issue
Block a user