diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 276690b93..3dabee4b0 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -66,6 +66,14 @@ App( sources=["modules/js_gui/text_input.c"], ) +App( + appid="js_gui__byte_input", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_byte_input_ep", + requires=["js_app", "js_gui", "js_event_loop"], + sources=["modules/js_gui/byte_input.c"], +) + App( appid="js_gui__text_box", apptype=FlipperAppType.PLUGIN, diff --git a/applications/system/js_app/modules/js_gui/byte_input.c b/applications/system/js_app/modules/js_gui/byte_input.c new file mode 100644 index 000000000..2e4096b28 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/byte_input.c @@ -0,0 +1,130 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +#define DEFAULT_BUF_SZ 4 + +typedef struct { + uint8_t* buffer; + size_t buffer_size; + FuriString* header; + FuriSemaphore* input_semaphore; + JsEventLoopContract contract; +} JsByteKbContext; + +static mjs_val_t + input_transformer(struct mjs* mjs, FuriSemaphore* semaphore, JsByteKbContext* context) { + furi_check(furi_semaphore_acquire(semaphore, 0) == FuriStatusOk); + return mjs_mk_array_buf(mjs, (char*)context->buffer, context->buffer_size); +} + +static void input_callback(JsByteKbContext* context) { + furi_semaphore_release(context->input_semaphore); +} + +static bool header_assign( + struct mjs* mjs, + ByteInput* input, + JsViewPropValue value, + JsByteKbContext* context) { + UNUSED(mjs); + furi_string_set(context->header, value.string); + byte_input_set_header_text(input, furi_string_get_cstr(context->header)); + return true; +} + +static bool + len_assign(struct mjs* mjs, ByteInput* input, JsViewPropValue value, JsByteKbContext* context) { + UNUSED(mjs); + UNUSED(input); + context->buffer_size = (size_t)(value.number); + context->buffer = realloc(context->buffer, context->buffer_size); //-V701 + return true; +} + +static bool default_data_assign( + struct mjs* mjs, + ByteInput* input, + JsViewPropValue value, + JsByteKbContext* context) { + UNUSED(mjs); + + if(mjs_is_data_view(value.array)) { + value.array = mjs_dataview_get_buf(mjs, value.array); + } + size_t default_data_len = 0; + char* default_data = mjs_array_buf_get_ptr(mjs, value.array, &default_data_len); + memcpy( + context->buffer, + (uint8_t*)default_data, + MIN((size_t)context->buffer_size, default_data_len)); + + byte_input_set_result_callback( + input, + (ByteInputCallback)input_callback, + NULL, + context, + context->buffer, + context->buffer_size); + return true; +} + +static JsByteKbContext* ctx_make(struct mjs* mjs, ByteInput* input, mjs_val_t view_obj) { + UNUSED(input); + JsByteKbContext* context = malloc(sizeof(JsByteKbContext)); + *context = (JsByteKbContext){ + .buffer_size = DEFAULT_BUF_SZ, + .buffer = malloc(DEFAULT_BUF_SZ), + .header = furi_string_alloc(), + .input_semaphore = furi_semaphore_alloc(1, 0), + }; + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeSemaphore, + .object = context->input_semaphore, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)input_transformer, + .transformer_context = context, + }, + }; + UNUSED(mjs); + UNUSED(view_obj); + mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void ctx_destroy(ByteInput* input, JsByteKbContext* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->input_semaphore); + furi_semaphore_free(context->input_semaphore); + furi_string_free(context->header); + free(context->buffer); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)byte_input_alloc, + .free = (JsViewFree)byte_input_free, + .get_view = (JsViewGetView)byte_input_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .prop_cnt = 3, + .props = { + (JsViewPropDescriptor){ + .name = "header", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)header_assign}, + (JsViewPropDescriptor){ + .name = "length", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)len_assign}, + (JsViewPropDescriptor){ + .name = "defaultData", + .type = JsViewPropTypeTypedArr, + .assign = (JsViewPropAssign)default_data_assign}, + }}; + +JS_GUI_VIEW_DEF(byte_input, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/js_gui.c b/applications/system/js_app/modules/js_gui/js_gui.c index 8ac3055d5..011f404e9 100644 --- a/applications/system/js_app/modules/js_gui/js_gui.c +++ b/applications/system/js_app/modules/js_gui/js_gui.c @@ -215,6 +215,20 @@ static bool } c_value = (JsViewPropValue){.array = value}; } break; + case JsViewPropTypeTypedArr: { + if(!mjs_is_typed_array(value)) { + expected_type = "typed_array"; + break; + } + c_value = (JsViewPropValue){.array = value}; + } break; + case JsViewPropTypeBool: { + if(!mjs_is_boolean(value)) { + expected_type = "bool"; + break; + } + c_value = (JsViewPropValue){.boolean = mjs_get_bool(mjs, value)}; + } break; } if(expected_type) { diff --git a/applications/system/js_app/modules/js_gui/js_gui.h b/applications/system/js_app/modules/js_gui/js_gui.h index 02198ca4f..67266b1fc 100644 --- a/applications/system/js_app/modules/js_gui/js_gui.h +++ b/applications/system/js_app/modules/js_gui/js_gui.h @@ -9,12 +9,15 @@ typedef enum { JsViewPropTypeString, JsViewPropTypeNumber, JsViewPropTypeArr, + JsViewPropTypeTypedArr, + JsViewPropTypeBool, } JsViewPropType; typedef union { const char* string; int32_t number; mjs_val_t array; + bool boolean; } JsViewPropValue; /** diff --git a/applications/system/js_app/modules/js_gui/text_input.c b/applications/system/js_app/modules/js_gui/text_input.c index 575029f8e..a43147e43 100644 --- a/applications/system/js_app/modules/js_gui/text_input.c +++ b/applications/system/js_app/modules/js_gui/text_input.c @@ -48,15 +48,40 @@ static bool max_len_assign( JsViewPropValue value, JsKbdContext* context) { UNUSED(mjs); + UNUSED(input); context->buffer_size = (size_t)(value.number + 1); context->buffer = realloc(context->buffer, context->buffer_size); //-V701 + return true; +} + +static bool default_text_assign( + struct mjs* mjs, + TextInput* input, + JsViewPropValue value, + JsKbdContext* context) { + UNUSED(mjs); + UNUSED(input); + + if(value.string) { + strlcpy(context->buffer, value.string, context->buffer_size); + } + return true; +} + +static bool default_text_clear_assign( + struct mjs* mjs, + TextInput* input, + JsViewPropValue value, + JsKbdContext* context) { + UNUSED(mjs); + text_input_set_result_callback( input, (TextInputCallback)input_callback, context, context->buffer, context->buffer_size, - true); + value.boolean); return true; } @@ -101,7 +126,7 @@ static const JsViewDescriptor view_descriptor = { .get_view = (JsViewGetView)text_input_get_view, .custom_make = (JsViewCustomMake)ctx_make, .custom_destroy = (JsViewCustomDestroy)ctx_destroy, - .prop_cnt = 3, + .prop_cnt = 5, .props = { (JsViewPropDescriptor){ .name = "header", @@ -115,6 +140,14 @@ static const JsViewDescriptor view_descriptor = { .name = "maxLength", .type = JsViewPropTypeNumber, .assign = (JsViewPropAssign)max_len_assign}, + (JsViewPropDescriptor){ + .name = "defaultText", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)default_text_assign}, + (JsViewPropDescriptor){ + .name = "defaultTextClear", + .type = JsViewPropTypeBool, + .assign = (JsViewPropAssign)default_text_clear_assign}, }}; JS_GUI_VIEW_DEF(text_input, &view_descriptor);