Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/lvgl-allocator.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: LVGL allocator checks
on:
push:
branches: [lvgl-textarea-buttonmatrix]
pull_request:
paths: [src/eez/core/alloc.h, tests/allocator-null.cpp]
permissions:
contents: read
jobs:
allocator:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- run: g++ -std=c++11 -fsanitize=address,undefined -Isrc tests/allocator-null.cpp -o /tmp/allocator-test
- run: /tmp/allocator-test
2 changes: 1 addition & 1 deletion src/eez/core/alloc.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ void free(void *ptr);
template<class T> struct ObjectAllocator {
static T *allocate(uint32_t id) {
auto ptr = alloc(sizeof(T), id);
return new (ptr) T;
return ptr ? new (ptr) T : nullptr;
}
static void deallocate(T* ptr) {
if (ptr) {
Expand Down
238 changes: 235 additions & 3 deletions src/eez/flow/components/lvgl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <stdio.h>
#include <string.h>
#include <eez/core/os.h>
#include <eez/core/alloc.h>

#include <eez/flow/components.h>
#include <eez/flow/flow_defs_v3.h>
Expand Down Expand Up @@ -510,7 +511,12 @@ const char *getFullObjectName(FlowState *flowState, const char *objectName) {
return; \
}\
propIndex++; \
const char *NAME = NAME##Value.toString(0xe42b3ca2).getString();
Value NAME##StringValue = NAME##Value.toString(0xe42b3ca2); \
const char *NAME = NAME##StringValue.getString(); \
if (!NAME) { \
throwError(flowState, componentIndex, FlowError::PropertyInAction(#NAME, actionName, actionIndex)); \
return; \
}

#define SCREEN_PROP(NAME) \
Value NAME##Value; \
Expand Down Expand Up @@ -1217,6 +1223,207 @@ ACTION_END

////////////////////////////////////////////////////////////////////////////////

ACTION_START(textareaGetText)
WIDGET_PROP(obj);
if (!lv_obj_check_type(obj, &lv_textarea_class)) {
throwError(flowState, componentIndex, FlowError::Plain("Expected a Textarea widget"));
return;
}
const char *text = lv_textarea_get_text(obj);
Value textCopy = Value::makeStringRef(text ? text : "", -1, 0x6f2c01a1);
if (!textCopy.isString()) {
throwError(flowState, componentIndex, FlowError::Plain("Failed to copy textarea text"));
return;
}
RESULT(result, textCopy);
ACTION_END

ACTION_START(textareaSetText)
WIDGET_PROP(obj);
if (!lv_obj_check_type(obj, &lv_textarea_class)) {
throwError(flowState, componentIndex, FlowError::Plain("Expected a Textarea widget"));
return;
}
STR_PROP(text);
lv_textarea_set_text(obj, text);
ACTION_END

ACTION_START(textareaSetOneLine)
WIDGET_PROP(obj);
if (!lv_obj_check_type(obj, &lv_textarea_class)) {
throwError(flowState, componentIndex, FlowError::Plain("Expected a Textarea widget"));
return;
}
BOOL_PROP(enabled);
lv_textarea_set_one_line(obj, enabled ? true : false);
ACTION_END

ACTION_START(textareaSetPasswordMode)
WIDGET_PROP(obj);
if (!lv_obj_check_type(obj, &lv_textarea_class)) {
throwError(flowState, componentIndex, FlowError::Plain("Expected a Textarea widget"));
return;
}
BOOL_PROP(enabled);
lv_textarea_set_password_mode(obj, enabled ? true : false);
ACTION_END

ACTION_START(textareaSetPasswordBullet)
WIDGET_PROP(obj);
if (!lv_obj_check_type(obj, &lv_textarea_class)) {
throwError(flowState, componentIndex, FlowError::Plain("Expected a Textarea widget"));
return;
}
STR_PROP(bullet);
lv_textarea_set_password_bullet(obj, bullet);
ACTION_END

ACTION_START(textareaSetPlaceholderText)
WIDGET_PROP(obj);
if (!lv_obj_check_type(obj, &lv_textarea_class)) {
throwError(flowState, componentIndex, FlowError::Plain("Expected a Textarea widget"));
return;
}
STR_PROP(text);
lv_textarea_set_placeholder_text(obj, text);
ACTION_END

ACTION_START(buttonMatrixGetSelectedButton)
WIDGET_PROP(obj);
#if LVGL_VERSION_MAJOR >= 9
if (!lv_obj_check_type(obj, &lv_buttonmatrix_class)) {
#else
if (!lv_obj_check_type(obj, &lv_btnmatrix_class)) {
#endif
throwError(flowState, componentIndex, FlowError::Plain("Expected a ButtonMatrix widget"));
return;
}
#if LVGL_VERSION_MAJOR >= 9
uint32_t selectedButton = lv_buttonmatrix_get_selected_button(obj);
#else
uint32_t selectedButton = (uint32_t)lv_btnmatrix_get_selected_btn(obj);
#endif
RESULT(result, Value((int)selectedButton, VALUE_TYPE_INT32));
ACTION_END

////////////////////////////////////////////////////////////////////////////////

ACTION_START(buttonMatrixGetButtonText)
WIDGET_PROP(obj);
#if LVGL_VERSION_MAJOR >= 9
if (!lv_obj_check_type(obj, &lv_buttonmatrix_class)) {
#else
if (!lv_obj_check_type(obj, &lv_btnmatrix_class)) {
#endif
throwError(flowState, componentIndex, FlowError::Plain("Expected a ButtonMatrix widget"));
return;
}
INT32_PROP(buttonID);
const char *text = nullptr;
// Validate before narrowing to v8's uint16_t or calling its getter at count.
// Use public APIs; row markers do not contribute to button IDs.
if (buttonID >= 0 && buttonID < UINT16_MAX) {
#if LVGL_VERSION_MAJOR >= 9
const char *const *map = lv_buttonmatrix_get_map(obj);
#else
const char *const *map = lv_btnmatrix_get_map(obj);
#endif
int32_t count = 0;
for (uint32_t i = 0; map && map[i] && map[i][0]; i++) {
if (strcmp(map[i], "\n") != 0) count++;
}
if (buttonID < count) {
#if LVGL_VERSION_MAJOR >= 9
text = lv_buttonmatrix_get_button_text(obj, (uint32_t)buttonID);
#else
text = lv_btnmatrix_get_btn_text(obj, (uint16_t)buttonID);
#endif
}
}
Value textCopy = Value::makeStringRef(text ? text : "", -1, 0x6f2c01a2);
if (!textCopy.isString()) {
throwError(flowState, componentIndex, FlowError::Plain("Failed to copy button text"));
return;
}
RESULT(result, textCopy);
ACTION_END

ACTION_START(buttonMatrixSetMap)
WIDGET_PROP(obj);
#if LVGL_VERSION_MAJOR >= 9
if (!lv_obj_check_type(obj, &lv_buttonmatrix_class)) {
#else
if (!lv_obj_check_type(obj, &lv_btnmatrix_class)) {
#endif
throwError(flowState, componentIndex, FlowError::Plain("Expected a ButtonMatrix widget"));
return;
}
if (properties.count % 2 != 1 || properties.count / 2 >= UINT16_MAX) {
throwError(flowState, componentIndex, FlowError::Plain("Invalid ButtonMatrix map properties"));
return;
}
uint32_t count = properties.count / 2;
// Retain all evaluated strings until the map helper has copied them.
Value texts = Value::makeArrayRef(count, 0, 0x6f2c01b0);
if (!texts.isArray()) {
throwError(flowState, componentIndex, FlowError::Plain("Failed to allocate ButtonMatrix map"));
return;
}
#if LVGL_VERSION_MAJOR >= 9
typedef lv_buttonmatrix_ctrl_t Ctrl;
#else
typedef lv_btnmatrix_ctrl_t Ctrl;
#endif
void *buffer = eez::alloc((count + 1) * sizeof(char *) + count * sizeof(Ctrl), 0x6f2c01b1);
if (!buffer) {
throwError(flowState, componentIndex, FlowError::Plain("Failed to allocate ButtonMatrix map"));
return;
}
const char **map = (const char **)buffer;
Ctrl *controls = (Ctrl *)(map + count + 1);
uint32_t buttonCount = 0;
bool valid = true;
for (uint32_t i = 0; i < count; i++) {
Value &text = texts.getArray()->values[i];
Value control;
if (!evalExpression(flowState, componentIndex, properties[propIndex++]->evalInstructions, text,
FlowError::PropertyInAction("buttons.text", actionName, actionIndex)) ||
!evalExpression(flowState, componentIndex, properties[propIndex++]->evalInstructions, control,
FlowError::PropertyInAction("buttons.width", actionName, actionIndex))) {
valid = false;
break;
}
// Match STR_PROP: expressions for string properties use Value::toString.
// Store the converted Value as well, not just a pointer to a temporary.
text = text.toString(0x6f2c01b2);
if (!text.isString() || !text.getString()) {
valid = false;
break;
}
int error;
int32_t ctrl = control.toInt32(&error);
if (error || ctrl < 0) {
valid = false;
break;
}
map[i] = text.getString()[0] ? text.getString() : " ";
if (ctrl == 0) {
if (strcmp(map[i], "\n")) { valid = false; break; }
} else {
if (!strcmp(map[i], "\n")) { valid = false; break; }
controls[buttonCount++] = (Ctrl)ctrl;
}
}
map[count] = NULL;
if (valid) valid = eez_flow_set_buttonmatrix_map(obj, map, count, buttonCount ? controls : NULL);
eez::free(buffer);
if (!valid) {
throwError(flowState, componentIndex, FlowError::Plain("Failed to set ButtonMatrix map"));
}
ACTION_END

////////////////////////////////////////////////////////////////////////////////

typedef void (*ActionType)(FlowState *flowState, unsigned componentIndex, const ListOfAssetsPtr<Property> &properties, uint32_t actionIndex);

static ActionType actions[] = {
Expand Down Expand Up @@ -1283,8 +1490,17 @@ static ActionType actions[] = {
/* 60 */ &tabviewSetActiveTab,
/* 61 */ &tabviewGetActiveTab,
/* 62 */ &arcRotateObjToAngle,
/* 61 */ &objGetDisplayX,
/* 62 */ &objGetDisplayY
/* 63 */ &objGetDisplayX,
/* 64 */ &objGetDisplayY,
/* 65 */ &textareaGetText,
/* 66 */ &textareaSetText,
/* 67 */ &textareaSetOneLine,
/* 68 */ &textareaSetPasswordMode,
/* 69 */ &textareaSetPasswordBullet,
/* 70 */ &textareaSetPlaceholderText,
/* 71 */ &buttonMatrixGetSelectedButton,
/* 72 */ &buttonMatrixSetMap,
/* 73 */ &buttonMatrixGetButtonText
};

////////////////////////////////////////////////////////////////////////////////
Expand All @@ -1304,6 +1520,22 @@ void executeLVGLApiComponent(FlowState *flowState, unsigned componentIndex) {
for (uint32_t actionIndex = executionState ? executionState->actionIndex : 0; actionIndex < component->actions.count; actionIndex++) {
// printf("actionIndex: %d\n", actionIndex + 1);
auto actionType = (LVGLApiComponent_ActionType *)component->actions[actionIndex];
if (actionType->action >= sizeof(actions) / sizeof(actions[0])) {
throwError(flowState, componentIndex, FlowError::Plain("Unknown LVGL action"));
return;
}
if (actionType->action >= 65 && actionType->action <= 71 && actionType->properties.count != 2) {
throwError(flowState, componentIndex, FlowError::Plain("Invalid LVGL action properties"));
return;
}
if (actionType->action == 72 && (actionType->properties.count % 2 != 1 || actionType->properties.count / 2 >= UINT16_MAX)) {
throwError(flowState, componentIndex, FlowError::Plain("Invalid ButtonMatrix map properties"));
return;
}
if (actionType->action == 73 && actionType->properties.count != 3) {
throwError(flowState, componentIndex, FlowError::Plain("Invalid LVGL action properties"));
return;
}
(*actions[actionType->action])(flowState, componentIndex, actionType->properties, actionIndex);
}

Expand Down
10 changes: 10 additions & 0 deletions src/eez/flow/lvgl_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,16 @@ EM_PORT_API(void) flowPropagateValueLVGLEvent(void *flowState, unsigned componen

static char textValue[EEZ_LVGL_TEMP_STRING_BUFFER_SIZE];

#include <eez/flow/lvgl_button_matrix.h>

extern "C" bool eez_flow_set_buttonmatrix_text(lv_obj_t *obj, uint32_t mapIndex, const char *text) {
return eez_bm_update_bound_text(obj, mapIndex, text);
}

extern "C" bool eez_flow_set_buttonmatrix_map(lv_obj_t *obj, const char *const *map, uint32_t count, const void *ctrl) {
return eez_bm_set_map(obj, map, count, (const eez_bm_ctrl_t *)ctrl);
}

extern "C" const char *_evalTextProperty(void *flowState, unsigned componentIndex, unsigned propertyIndex, const char *errorMessage, const char *file, int line) {
eez::Value value;
if (!eez::flow::evalProperty((eez::flow::FlowState *)flowState, componentIndex, propertyIndex, value, eez::flow::FlowError::Plain(errorMessage, file, line))) {
Expand Down
6 changes: 6 additions & 0 deletions src/eez/flow/lvgl_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ void eez_flow_init_fonts(const ext_font_desc_t *fonts, size_t numFonts);
void eez_flow_set_create_screen_func(void (*createScreenFunc)(int screenIndex));
void eez_flow_set_delete_screen_func(void (*deleteScreenFunc)(int screenIndex));

// Updates original widget bindings until Set Map succeeds.
// Copies map text; caller retains ownership of all input buffers.
bool eez_flow_set_buttonmatrix_text(lv_obj_t *obj, uint32_t mapIndex, const char *text);
// Replaces the whole map and detaches its original widget text bindings.
bool eez_flow_set_buttonmatrix_map(lv_obj_t *obj, const char *const *map, uint32_t count, const void *ctrl);

void eez_flow_tick();

bool eez_flow_is_stopped();
Expand Down
Loading