From c673b53e21ea6e8ac57e6cf9823929521d0ddba7 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 17 May 2024 17:45:40 +0100 Subject: [PATCH 1/3] JS: Add math module (#3598) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * JS: Add math module * Double constants * Error on argument type mismatch * Fix missing returns * Using sin, exp from c library * asin, acos, pow, sqrt too * Js: tests for math module and various fixes. Co-authored-by: あく Co-authored-by: nminaylov --- applications/system/js_app/application.fam | 8 + .../js_app/examples/apps/Scripts/math.js | 69 ++++ applications/system/js_app/js_thread.c | 2 +- applications/system/js_app/modules/js_math.c | 354 ++++++++++++++++++ lib/mjs/mjs_core.c | 2 +- 5 files changed, 433 insertions(+), 2 deletions(-) create mode 100644 applications/system/js_app/examples/apps/Scripts/math.js create mode 100644 applications/system/js_app/modules/js_math.c diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index a955ef355..920e888cc 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -54,3 +54,11 @@ App( requires=["js_app"], sources=["modules/js_submenu.c"], ) + +App( + appid="js_math", + apptype=FlipperAppType.PLUGIN, + entry_point="js_math_ep", + requires=["js_app"], + sources=["modules/js_math.c"], +) diff --git a/applications/system/js_app/examples/apps/Scripts/math.js b/applications/system/js_app/examples/apps/Scripts/math.js new file mode 100644 index 000000000..c5a0bf18d --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/math.js @@ -0,0 +1,69 @@ +let math = require("math"); + +print("math.abs(-5):", math.abs(-5)); +print("math.acos(0.5):", math.acos(0.5)); +print("math.acosh(2):", math.acosh(2)); +print("math.asin(0.5):", math.asin(0.5)); +print("math.asinh(2):", math.asinh(2)); +print("math.atan(1):", math.atan(1)); +print("math.atan2(1, 1):", math.atan2(1, 1)); +print("math.atanh(0.5):", math.atanh(0.5)); +print("math.cbrt(27):", math.cbrt(27)); +print("math.ceil(5.3):", math.ceil(5.3)); +print("math.clz32(1):", math.clz32(1)); +print("math.cos(math.PI):", math.cos(math.PI)); +print("math.exp(1):", math.exp(1)); +print("math.floor(5.7):", math.floor(5.7)); +print("math.max(3, 5):", math.max(3, 5)); +print("math.min(3, 5):", math.min(3, 5)); +print("math.pow(2, 3):", math.pow(2, 3)); +print("math.random():", math.random()); +print("math.sign(-5):", math.sign(-5)); +print("math.sin(math.PI/2):", math.sin(math.PI / 2)); +print("math.sqrt(25):", math.sqrt(25)); +print("math.trunc(5.7):", math.trunc(5.7)); + +// Unit tests. Please add more if you have time and knowledge. +// math.EPSILON on Flipper Zero is 2.22044604925031308085e-16 + +let succeeded = 0; +let failed = 0; + +function test(text, result, expected, epsilon) { + let is_equal = math.is_equal(result, expected, epsilon); + if (is_equal) { + succeeded += 1; + } else { + failed += 1; + print(text, "expected", expected, "got", result); + } +} + +test("math.abs(5)", math.abs(-5), 5, math.EPSILON); +test("math.abs(0.5)", math.abs(-0.5), 0.5, math.EPSILON); +test("math.abs(5)", math.abs(5), 5, math.EPSILON); +test("math.abs(-0.5)", math.abs(0.5), 0.5, math.EPSILON); +test("math.acos(0.5)", math.acos(0.5), 1.0471975511965976, math.EPSILON); +test("math.acosh(2)", math.acosh(2), 1.3169578969248166, math.EPSILON); +test("math.asin(0.5)", math.asin(0.5), 0.5235987755982988, math.EPSILON); +test("math.asinh(2)", math.asinh(2), 1.4436354751788103, math.EPSILON); +test("math.atan(1)", math.atan(1), 0.7853981633974483, math.EPSILON); +test("math.atan2(1, 1)", math.atan2(1, 1), 0.7853981633974483, math.EPSILON); +test("math.atanh(0.5)", math.atanh(0.5), 0.5493061443340549, math.EPSILON); +test("math.cbrt(27)", math.cbrt(27), 3, math.EPSILON); +test("math.ceil(5.3)", math.ceil(5.3), 6, math.EPSILON); +test("math.clz32(1)", math.clz32(1), 31, math.EPSILON); +test("math.floor(5.7)", math.floor(5.7), 5, math.EPSILON); +test("math.max(3, 5)", math.max(3, 5), 5, math.EPSILON); +test("math.min(3, 5)", math.min(3, 5), 3, math.EPSILON); +test("math.pow(2, 3)", math.pow(2, 3), 8, math.EPSILON); +test("math.sign(-5)", math.sign(-5), -1, math.EPSILON); +test("math.sqrt(25)", math.sqrt(25), 5, math.EPSILON); +test("math.trunc(5.7)", math.trunc(5.7), 5, math.EPSILON); +test("math.cos(math.PI)", math.cos(math.PI), -1, math.EPSILON * 18); // Error 3.77475828372553223744e-15 +test("math.exp(1)", math.exp(1), 2.718281828459045, math.EPSILON * 2); // Error 4.44089209850062616169e-16 +test("math.sin(math.PI / 2)", math.sin(math.PI / 2), 1, math.EPSILON * 4.5); // Error 9.99200722162640886381e-16 + +if (failed > 0) { + print("!!!", failed, "Unit tests failed !!!"); +} \ No newline at end of file diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index 759d63b0e..78b6f6ff4 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -285,7 +285,7 @@ static int32_t js_thread(void* arg) { } const char* stack_trace = mjs_get_stack_trace(mjs); if(stack_trace != NULL) { - FURI_LOG_E(TAG, "Stack trace:\n%s", stack_trace); + FURI_LOG_E(TAG, "Stack trace:\r\n%s", stack_trace); if(worker->app_callback) { worker->app_callback(JsThreadEventErrorTrace, stack_trace, worker->context); } diff --git a/applications/system/js_app/modules/js_math.c b/applications/system/js_app/modules/js_math.c new file mode 100644 index 000000000..766156818 --- /dev/null +++ b/applications/system/js_app/modules/js_math.c @@ -0,0 +1,354 @@ +#include "../js_modules.h" +#include "furi_hal_random.h" +#include + +#define JS_MATH_PI ((double)M_PI) +#define JS_MATH_E ((double)M_E) +#define JS_MATH_EPSILON ((double)DBL_EPSILON) + +#define TAG "JsMath" + +static void ret_bad_args(struct mjs* mjs, const char* error) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); + mjs_return(mjs, MJS_UNDEFINED); +} + +static bool check_args(struct mjs* mjs, size_t count) { + size_t num_args = mjs_nargs(mjs); + if(num_args != count) { + ret_bad_args(mjs, "Wrong argument count"); + return false; + } + for(size_t i = 0; i < count; i++) { + if(!mjs_is_number(mjs_arg(mjs, i))) { + ret_bad_args(mjs, "Wrong argument type"); + return false; + } + } + return true; +} + +void js_math_is_equal(struct mjs* mjs) { + if(!check_args(mjs, 3)) { + return; + } + + double a = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double b = mjs_get_double(mjs, mjs_arg(mjs, 1)); + double e = mjs_get_double(mjs, mjs_arg(mjs, 2)); + double f = fabs(a - b); + + mjs_return(mjs, mjs_mk_boolean(mjs, (f <= e))); +} + +void js_math_abs(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, fabs(x))); +} + +void js_math_acos(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x < (double)-1. || x > (double)1.) { + ret_bad_args(mjs, "Invalid input value for math.acos"); + return; + } + + mjs_return(mjs, mjs_mk_number(mjs, acos(x))); +} + +void js_math_acosh(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x < (double)1.) { + ret_bad_args(mjs, "Invalid input value for math.acosh"); + return; + } + + mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x - (double)1.)))); +} + +void js_math_asin(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, asin(x))); +} + +void js_math_asinh(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x + (double)1.)))); +} + +void js_math_atan(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, atan(x))); +} + +void js_math_atan2(struct mjs* mjs) { + if(!check_args(mjs, 2)) { + return; + } + + double y = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double x = mjs_get_double(mjs, mjs_arg(mjs, 1)); + + mjs_return(mjs, mjs_mk_number(mjs, atan2(y, x))); +} + +void js_math_atanh(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x < (double)-1. || x > (double)1.) { + ret_bad_args(mjs, "Invalid input value for math.atanh"); + return; + } + + mjs_return(mjs, mjs_mk_number(mjs, (double)0.5 * log(((double)1. + x) / ((double)1. - x)))); +} + +void js_math_cbrt(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, cbrt(x))); +} + +void js_math_ceil(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, mjs_mk_number(mjs, ceil(x))); +} + +void js_math_clz32(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + unsigned int x = (unsigned int)mjs_get_int(mjs, mjs_arg(mjs, 0)); + int count = 0; + while(x) { + x >>= 1; + count++; + } + + mjs_return(mjs, mjs_mk_number(mjs, 32 - count)); +} + +void js_math_cos(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, cos(x))); +} + +void js_math_exp(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, exp(x))); +} + +void js_math_floor(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, floor(x))); +} + +void js_math_log(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x <= 0) { + ret_bad_args(mjs, "Invalid input value for math.log"); + return; + } + + mjs_return(mjs, mjs_mk_number(mjs, log(x))); +} + +void js_math_max(struct mjs* mjs) { + if(!check_args(mjs, 2)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double y = mjs_get_double(mjs, mjs_arg(mjs, 1)); + + mjs_return(mjs, mjs_mk_number(mjs, x > y ? x : y)); +} + +void js_math_min(struct mjs* mjs) { + if(!check_args(mjs, 2)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double y = mjs_get_double(mjs, mjs_arg(mjs, 1)); + + mjs_return(mjs, mjs_mk_number(mjs, x < y ? x : y)); +} + +void js_math_pow(struct mjs* mjs) { + if(!check_args(mjs, 2)) { + return; + } + + double base = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double exponent = mjs_get_double(mjs, mjs_arg(mjs, 1)); + + mjs_return(mjs, mjs_mk_number(mjs, pow(base, exponent))); +} + +void js_math_random(struct mjs* mjs) { + if(!check_args(mjs, 0)) { + return; + } + + // double clearly provides more bits for entropy then we pack + // 32bit should be enough for now, but fix it maybe + const uint32_t random_val = furi_hal_random_get(); + double rnd = (double)random_val / (double)FURI_HAL_RANDOM_MAX; + + mjs_return(mjs, mjs_mk_number(mjs, rnd)); +} + +void js_math_sign(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return( + mjs, + mjs_mk_number(mjs, x == (double)0. ? 0 : (x < (double)0. ? (double)-1.0 : (double)1.0))); +} + +void js_math_sin(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, sin(x))); +} + +void js_math_sqrt(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x < (double)0.) { + ret_bad_args(mjs, "Invalid input value for math.sqrt"); + return; + } + + mjs_return(mjs, mjs_mk_number(mjs, sqrt(x))); +} + +void js_math_trunc(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, x < (double)0. ? ceil(x) : floor(x))); +} + +static void* js_math_create(struct mjs* mjs, mjs_val_t* object) { + mjs_val_t math_obj = mjs_mk_object(mjs); + mjs_set(mjs, math_obj, "is_equal", ~0, MJS_MK_FN(js_math_is_equal)); + mjs_set(mjs, math_obj, "abs", ~0, MJS_MK_FN(js_math_abs)); + mjs_set(mjs, math_obj, "acos", ~0, MJS_MK_FN(js_math_acos)); + mjs_set(mjs, math_obj, "acosh", ~0, MJS_MK_FN(js_math_acosh)); + mjs_set(mjs, math_obj, "asin", ~0, MJS_MK_FN(js_math_asin)); + mjs_set(mjs, math_obj, "asinh", ~0, MJS_MK_FN(js_math_asinh)); + mjs_set(mjs, math_obj, "atan", ~0, MJS_MK_FN(js_math_atan)); + mjs_set(mjs, math_obj, "atan2", ~0, MJS_MK_FN(js_math_atan2)); + mjs_set(mjs, math_obj, "atanh", ~0, MJS_MK_FN(js_math_atanh)); + mjs_set(mjs, math_obj, "cbrt", ~0, MJS_MK_FN(js_math_cbrt)); + mjs_set(mjs, math_obj, "ceil", ~0, MJS_MK_FN(js_math_ceil)); + mjs_set(mjs, math_obj, "clz32", ~0, MJS_MK_FN(js_math_clz32)); + mjs_set(mjs, math_obj, "cos", ~0, MJS_MK_FN(js_math_cos)); + mjs_set(mjs, math_obj, "exp", ~0, MJS_MK_FN(js_math_exp)); + mjs_set(mjs, math_obj, "floor", ~0, MJS_MK_FN(js_math_floor)); + mjs_set(mjs, math_obj, "log", ~0, MJS_MK_FN(js_math_log)); + mjs_set(mjs, math_obj, "max", ~0, MJS_MK_FN(js_math_max)); + mjs_set(mjs, math_obj, "min", ~0, MJS_MK_FN(js_math_min)); + mjs_set(mjs, math_obj, "pow", ~0, MJS_MK_FN(js_math_pow)); + mjs_set(mjs, math_obj, "random", ~0, MJS_MK_FN(js_math_random)); + mjs_set(mjs, math_obj, "sign", ~0, MJS_MK_FN(js_math_sign)); + mjs_set(mjs, math_obj, "sin", ~0, MJS_MK_FN(js_math_sin)); + mjs_set(mjs, math_obj, "sqrt", ~0, MJS_MK_FN(js_math_sqrt)); + mjs_set(mjs, math_obj, "trunc", ~0, MJS_MK_FN(js_math_trunc)); + mjs_set(mjs, math_obj, "PI", ~0, mjs_mk_number(mjs, JS_MATH_PI)); + mjs_set(mjs, math_obj, "E", ~0, mjs_mk_number(mjs, JS_MATH_E)); + mjs_set(mjs, math_obj, "EPSILON", ~0, mjs_mk_number(mjs, JS_MATH_EPSILON)); + *object = math_obj; + return (void*)1; +} + +static const JsModuleDescriptor js_math_desc = { + "math", + js_math_create, + NULL, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_math_desc, +}; + +const FlipperAppPluginDescriptor* js_math_ep(void) { + return &plugin_descriptor; +} diff --git a/lib/mjs/mjs_core.c b/lib/mjs/mjs_core.c index aae196599..bcdcb364a 100644 --- a/lib/mjs/mjs_core.c +++ b/lib/mjs/mjs_core.c @@ -280,7 +280,7 @@ static void mjs_append_stack_trace_line(struct mjs* mjs, size_t offset) { const char* filename = mjs_get_bcode_filename_by_offset(mjs, offset); int line_no = mjs_get_lineno_by_offset(mjs, offset); char* new_line = NULL; - const char* fmt = "at %s:%d\n"; + const char* fmt = "\tat %s:%d\r\n"; if(filename == NULL) { // fprintf( // stderr, From 0d456aa5505ca7361aba50011db30ffaf10633e2 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 17 May 2024 18:43:52 +0100 Subject: [PATCH 2/3] JS: Add textbox module (#3597) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * JS: Add textbox module * Using view_holder instead of view_dispatcher, more checks in js_textbox_show * API version sync * Rename emptyText() to clearText() * Keeping view_holder allocated for thread sefety * Js: proper comparision with 0 in js_math_sign * Js: add comments and fix condition race in textbox Co-authored-by: あく Co-authored-by: nminaylov --- applications/system/js_app/application.fam | 8 + .../js_app/examples/apps/Scripts/textbox.js | 30 +++ applications/system/js_app/modules/js_math.c | 3 +- .../system/js_app/modules/js_textbox.c | 220 ++++++++++++++++++ 4 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 applications/system/js_app/examples/apps/Scripts/textbox.js create mode 100644 applications/system/js_app/modules/js_textbox.c diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 920e888cc..a7ae5c7c7 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -62,3 +62,11 @@ App( requires=["js_app"], sources=["modules/js_math.c"], ) + +App( + appid="js_textbox", + apptype=FlipperAppType.PLUGIN, + entry_point="js_textbox_ep", + requires=["js_app"], + sources=["modules/js_textbox.c"], +) diff --git a/applications/system/js_app/examples/apps/Scripts/textbox.js b/applications/system/js_app/examples/apps/Scripts/textbox.js new file mode 100644 index 000000000..6caf37234 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/textbox.js @@ -0,0 +1,30 @@ +let textbox = require("textbox"); + +// You should set config before adding text +// Focus (start / end), Font (text / hex) +textbox.setConfig("end", "text"); + +// Can make sure it's cleared before showing, in case of reusing in same script +// (Closing textbox already clears the text, but maybe you added more in a loop for example) +textbox.clearText(); + +// Add default text +textbox.addText("Example dynamic updating textbox\n"); + +// Non-blocking, can keep updating text after, can close in JS or in GUI +textbox.show(); + +let i = 0; +while (textbox.isOpen() && i < 20) { + print("console", i++); + + // Add text to textbox buffer + textbox.addText("textbox " + to_string(i) + "\n"); + + delay(500); +} + +// If not closed by user (instead i < 20 is false above), close forcefully +if (textbox.isOpen()) { + textbox.close(); +} diff --git a/applications/system/js_app/modules/js_math.c b/applications/system/js_app/modules/js_math.c index 766156818..b4c1cdca2 100644 --- a/applications/system/js_app/modules/js_math.c +++ b/applications/system/js_app/modules/js_math.c @@ -267,7 +267,8 @@ void js_math_sign(struct mjs* mjs) { mjs_return( mjs, - mjs_mk_number(mjs, x == (double)0. ? 0 : (x < (double)0. ? (double)-1.0 : (double)1.0))); + mjs_mk_number( + mjs, fabs(x) <= JS_MATH_EPSILON ? 0 : (x < (double)0. ? (double)-1.0 : (double)1.0))); } void js_math_sin(struct mjs* mjs) { diff --git a/applications/system/js_app/modules/js_textbox.c b/applications/system/js_app/modules/js_textbox.c new file mode 100644 index 000000000..d15cd2779 --- /dev/null +++ b/applications/system/js_app/modules/js_textbox.c @@ -0,0 +1,220 @@ +#include +#include +#include "../js_modules.h" + +typedef struct { + TextBox* text_box; + ViewHolder* view_holder; + FuriString* text; + bool is_shown; +} JsTextboxInst; + +static JsTextboxInst* get_this_ctx(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsTextboxInst* textbox = mjs_get_ptr(mjs, obj_inst); + furi_assert(textbox); + return textbox; +} + +static void ret_bad_args(struct mjs* mjs, const char* error) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); + mjs_return(mjs, MJS_UNDEFINED); +} + +static bool check_arg_count(struct mjs* mjs, size_t count) { + size_t num_args = mjs_nargs(mjs); + if(num_args != count) { + ret_bad_args(mjs, "Wrong argument count"); + return false; + } + return true; +} + +static void js_textbox_set_config(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 2)) return; + + TextBoxFocus set_focus = TextBoxFocusStart; + mjs_val_t focus_arg = mjs_arg(mjs, 0); + const char* focus = mjs_get_string(mjs, &focus_arg, NULL); + if(!focus) { + ret_bad_args(mjs, "Focus must be a string"); + return; + } else { + if(!strncmp(focus, "start", strlen("start"))) { + set_focus = TextBoxFocusStart; + } else if(!strncmp(focus, "end", strlen("end"))) { + set_focus = TextBoxFocusEnd; + } else { + ret_bad_args(mjs, "Bad focus value"); + return; + } + } + + TextBoxFont set_font = TextBoxFontText; + mjs_val_t font_arg = mjs_arg(mjs, 1); + const char* font = mjs_get_string(mjs, &font_arg, NULL); + if(!font) { + ret_bad_args(mjs, "Font must be a string"); + return; + } else { + if(!strncmp(font, "text", strlen("text"))) { + set_font = TextBoxFontText; + } else if(!strncmp(font, "hex", strlen("hex"))) { + set_font = TextBoxFontHex; + } else { + ret_bad_args(mjs, "Bad font value"); + return; + } + } + + text_box_set_focus(textbox->text_box, set_focus); + text_box_set_font(textbox->text_box, set_font); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_textbox_add_text(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 1)) return; + + mjs_val_t text_arg = mjs_arg(mjs, 0); + size_t text_len = 0; + const char* text = mjs_get_string(mjs, &text_arg, &text_len); + if(!text) { + ret_bad_args(mjs, "Text must be a string"); + return; + } + + // Avoid condition race between GUI and JS thread + text_box_set_text(textbox->text_box, ""); + + size_t new_len = furi_string_size(textbox->text) + text_len; + if(new_len >= 4096) { + furi_string_right(textbox->text, new_len / 2); + } + + furi_string_cat(textbox->text, text); + + text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text)); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_textbox_clear_text(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + + // Avoid condition race between GUI and JS thread + text_box_set_text(textbox->text_box, ""); + + furi_string_reset(textbox->text); + + text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text)); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_textbox_is_open(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + + mjs_return(mjs, mjs_mk_boolean(mjs, textbox->is_shown)); +} + +static void textbox_callback(void* context, uint32_t arg) { + UNUSED(arg); + JsTextboxInst* textbox = context; + view_holder_stop(textbox->view_holder); + textbox->is_shown = false; +} + +static void textbox_exit(void* context) { + JsTextboxInst* textbox = context; + // Using timer to schedule view_holder stop, will not work under high CPU load + furi_timer_pending_callback(textbox_callback, textbox, 0); +} + +static void js_textbox_show(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + + if(textbox->is_shown) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Textbox is already shown"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + view_holder_start(textbox->view_holder); + textbox->is_shown = true; + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_textbox_close(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + + view_holder_stop(textbox->view_holder); + textbox->is_shown = false; + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void* js_textbox_create(struct mjs* mjs, mjs_val_t* object) { + JsTextboxInst* textbox = malloc(sizeof(JsTextboxInst)); + + mjs_val_t textbox_obj = mjs_mk_object(mjs); + mjs_set(mjs, textbox_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, textbox)); + mjs_set(mjs, textbox_obj, "setConfig", ~0, MJS_MK_FN(js_textbox_set_config)); + mjs_set(mjs, textbox_obj, "addText", ~0, MJS_MK_FN(js_textbox_add_text)); + mjs_set(mjs, textbox_obj, "clearText", ~0, MJS_MK_FN(js_textbox_clear_text)); + mjs_set(mjs, textbox_obj, "isOpen", ~0, MJS_MK_FN(js_textbox_is_open)); + mjs_set(mjs, textbox_obj, "show", ~0, MJS_MK_FN(js_textbox_show)); + mjs_set(mjs, textbox_obj, "close", ~0, MJS_MK_FN(js_textbox_close)); + + textbox->text = furi_string_alloc(); + textbox->text_box = text_box_alloc(); + + Gui* gui = furi_record_open(RECORD_GUI); + textbox->view_holder = view_holder_alloc(); + view_holder_attach_to_gui(textbox->view_holder, gui); + view_holder_set_back_callback(textbox->view_holder, textbox_exit, textbox); + view_holder_set_view(textbox->view_holder, text_box_get_view(textbox->text_box)); + + *object = textbox_obj; + return textbox; +} + +static void js_textbox_destroy(void* inst) { + JsTextboxInst* textbox = inst; + + view_holder_stop(textbox->view_holder); + view_holder_free(textbox->view_holder); + textbox->view_holder = NULL; + + furi_record_close(RECORD_GUI); + + text_box_reset(textbox->text_box); + furi_string_reset(textbox->text); + + text_box_free(textbox->text_box); + furi_string_free(textbox->text); + free(textbox); +} + +static const JsModuleDescriptor js_textbox_desc = { + "textbox", + js_textbox_create, + js_textbox_destroy, +}; + +static const FlipperAppPluginDescriptor textbox_plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_textbox_desc, +}; + +const FlipperAppPluginDescriptor* js_textbox_ep(void) { + return &textbox_plugin_descriptor; +} \ No newline at end of file From de4b086083b8f9914631969c236505ac8c83ee5d Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 18 May 2024 19:40:01 +0100 Subject: [PATCH 3/3] Fix calling both `view_free_model()` and `view_free()` (#3655) --- applications/services/gui/view.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/services/gui/view.c b/applications/services/gui/view.c index 8fc5c2699..a35c2fa38 100644 --- a/applications/services/gui/view.c +++ b/applications/services/gui/view.c @@ -97,10 +97,11 @@ void view_free_model(View* view) { furi_mutex_free(model->mutex); free(model->data); free(model); - view->model = NULL; } else { furi_crash(); } + view->model = NULL; + view->model_type = ViewModelTypeNone; } void* view_get_model(View* view) {