From 933134ed94428d1fb92081c970e0f824d12060a8 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 1 Apr 2025 14:35:11 +0400 Subject: [PATCH 1/2] [FL-3962] BadUSB arbitrary key combinations (#4156) * badusb: arbitrary modifier key combinations * badusb: format code * badusb: add const * Revert "badusb: add const" This reverts commit 6ae909fd5d3a5b614712fc92fadda98a6ced2893. --------- Co-authored-by: hedger --- .../main/bad_usb/helpers/ducky_script.c | 42 +++++++++++-------- .../main/bad_usb/helpers/ducky_script_i.h | 2 + .../bad_usb/helpers/ducky_script_keycodes.c | 28 +++++++++---- 3 files changed, 46 insertions(+), 26 deletions(-) diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index e71c03c48..99034028d 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -40,11 +40,8 @@ static const uint8_t numpad_keys[10] = { }; uint32_t ducky_get_command_len(const char* line) { - uint32_t len = strlen(line); - for(uint32_t i = 0; i < len; i++) { - if(line[i] == ' ') return i; - } - return 0; + char* first_space = strchr(line, ' '); + return first_space ? (first_space - line) : 0; } bool ducky_is_line_end(const char chr) { @@ -180,37 +177,46 @@ static bool ducky_string_next(BadUsbScript* bad_usb) { static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { uint32_t line_len = furi_string_size(line); - const char* line_tmp = furi_string_get_cstr(line); + const char* line_cstr = furi_string_get_cstr(line); if(line_len == 0) { return SCRIPT_STATE_NEXT_LINE; // Skip empty lines } - FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp); + FURI_LOG_D(WORKER_TAG, "line:%s", line_cstr); // Ducky Lang Functions - int32_t cmd_result = ducky_execute_cmd(bad_usb, line_tmp); + int32_t cmd_result = ducky_execute_cmd(bad_usb, line_cstr); if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) { return cmd_result; } // Mouse Keys - uint16_t key = ducky_get_mouse_keycode_by_name(line_tmp); + uint16_t key = ducky_get_mouse_keycode_by_name(line_cstr); if(key != HID_MOUSE_INVALID) { bad_usb->hid->mouse_press(bad_usb->hid_inst, key); bad_usb->hid->mouse_release(bad_usb->hid_inst, key); return 0; } - // Special keys + modifiers - key = ducky_get_keycode(bad_usb, line_tmp, false); - if(key == HID_KEYBOARD_NONE) { - return ducky_error(bad_usb, "No keycode defined for %s", line_tmp); - } - if((key & 0xFF00) != 0) { - // It's a modifier key - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - key |= ducky_get_keycode(bad_usb, line_tmp, true); + // Parse chain of modifiers linked by spaces and hyphens + uint16_t modifiers = 0; + while(1) { + key = ducky_get_next_modifier_keycode_by_name(&line_cstr); + if(key == HID_KEYBOARD_NONE) break; + + modifiers |= key; + char next_char = *line_cstr; + if(next_char == ' ' || next_char == '-') line_cstr++; } + + // Main key + char next_char = *line_cstr; + uint16_t main_key = ducky_get_keycode_by_name(line_cstr); + if(!main_key && next_char) main_key = BADUSB_ASCII_TO_KEY(bad_usb, next_char); + key = modifiers | main_key; + + if(key == 0 && next_char) ducky_error(bad_usb, "No keycode defined for %s", line_cstr); + bad_usb->hid->kb_press(bad_usb->hid_inst, key); bad_usb->hid->kb_release(bad_usb->hid_inst, key); return 0; diff --git a/applications/main/bad_usb/helpers/ducky_script_i.h b/applications/main/bad_usb/helpers/ducky_script_i.h index fd95ecf58..2b15c2586 100644 --- a/applications/main/bad_usb/helpers/ducky_script_i.h +++ b/applications/main/bad_usb/helpers/ducky_script_i.h @@ -54,6 +54,8 @@ uint32_t ducky_get_command_len(const char* line); bool ducky_is_line_end(const char chr); +uint16_t ducky_get_next_modifier_keycode_by_name(const char** param); + uint16_t ducky_get_keycode_by_name(const char* param); uint16_t ducky_get_media_keycode_by_name(const char* param); diff --git a/applications/main/bad_usb/helpers/ducky_script_keycodes.c b/applications/main/bad_usb/helpers/ducky_script_keycodes.c index 7dd2e4d16..ce957bb4e 100644 --- a/applications/main/bad_usb/helpers/ducky_script_keycodes.c +++ b/applications/main/bad_usb/helpers/ducky_script_keycodes.c @@ -6,21 +6,16 @@ typedef struct { uint16_t keycode; } DuckyKey; -static const DuckyKey ducky_keys[] = { - {"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT}, - {"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT}, - {"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT}, - {"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI}, - {"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT}, - {"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL}, - +static const DuckyKey ducky_modifier_keys[] = { {"CTRL", KEY_MOD_LEFT_CTRL}, {"CONTROL", KEY_MOD_LEFT_CTRL}, {"SHIFT", KEY_MOD_LEFT_SHIFT}, {"ALT", KEY_MOD_LEFT_ALT}, {"GUI", KEY_MOD_LEFT_GUI}, {"WINDOWS", KEY_MOD_LEFT_GUI}, +}; +static const DuckyKey ducky_keys[] = { {"DOWNARROW", HID_KEYBOARD_DOWN_ARROW}, {"DOWN", HID_KEYBOARD_DOWN_ARROW}, {"LEFTARROW", HID_KEYBOARD_LEFT_ARROW}, @@ -119,6 +114,23 @@ static const DuckyKey ducky_mouse_keys[] = { {"WHEEL_CLICK", HID_MOUSE_BTN_WHEEL}, }; +uint16_t ducky_get_next_modifier_keycode_by_name(const char** param) { + const char* input_str = *param; + + for(size_t i = 0; i < COUNT_OF(ducky_modifier_keys); i++) { + size_t key_cmd_len = strlen(ducky_modifier_keys[i].name); + if((strncmp(input_str, ducky_modifier_keys[i].name, key_cmd_len) == 0)) { + char next_char_after_key = input_str[key_cmd_len]; + if(ducky_is_line_end(next_char_after_key) || (next_char_after_key == '-')) { + *param = &input_str[key_cmd_len]; + return ducky_modifier_keys[i].keycode; + } + } + } + + return HID_KEYBOARD_NONE; +} + uint16_t ducky_get_keycode_by_name(const char* param) { for(size_t i = 0; i < COUNT_OF(ducky_keys); i++) { size_t key_cmd_len = strlen(ducky_keys[i].name); From d34ff3310dd187e91f2c345fa7fa8b8136670551 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Tue, 1 Apr 2025 11:02:12 +0000 Subject: [PATCH 2/2] JS: Update and fix docs, fix Number.toString() with decimals (#4168) * Update and fix JS docs This could really use some automation, atleast for API reference There are TypeScript definitions and typedocs, we don't need to be monkeys copying and reformatting this to API reference by hand * Fix bugged character * JS: Fix Number.toString() with decimals * Fix * Forgot this one * docs: mention per-view child format * Added @portasynthinca3 to docs' codeowners * Updated CODEOWNERS --------- Co-authored-by: Anna Antonenko Co-authored-by: hedger Co-authored-by: hedger --- .github/CODEOWNERS | 8 +- .../file_formats/SubGhzFileFormats.md | 2 +- documentation/js/js_badusb.md | 47 ++- documentation/js/js_builtin.md | 281 +++++++++++++++++- documentation/js/js_data_types.md | 4 +- .../js/js_developing_apps_using_js_sdk.md | 21 ++ documentation/js/js_flipper.md | 52 ++++ documentation/js/js_gpio.md | 32 +- documentation/js/js_gui.md | 41 ++- documentation/js/js_gui__byte_input.md | 32 ++ documentation/js/js_gui__file_picker.md | 20 ++ documentation/js/js_gui__icon.md | 32 ++ documentation/js/js_gui__text_box.md | 4 +- documentation/js/js_gui__text_input.md | 8 +- documentation/js/js_gui__widget.md | 13 + documentation/js/js_math.md | 20 +- documentation/js/js_serial.md | 46 ++- documentation/js/js_storage.md | 32 +- lib/mjs/mjs_primitive.c | 25 +- 19 files changed, 675 insertions(+), 45 deletions(-) create mode 100644 documentation/js/js_flipper.md create mode 100644 documentation/js/js_gui__byte_input.md create mode 100644 documentation/js/js_gui__file_picker.md create mode 100644 documentation/js/js_gui__icon.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dcf964add..ef8b79370 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -18,7 +18,7 @@ /applications/main/gpio/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov /applications/main/ibutton/ @skotopes @DrZlo13 @hedger @gsurkov /applications/main/infrared/ @skotopes @DrZlo13 @hedger @gsurkov -/applications/main/nfc/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich @Astrrra +/applications/main/nfc/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich /applications/main/subghz/ @skotopes @DrZlo13 @hedger @gsurkov @Skorpionm /applications/main/u2f/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov @@ -38,7 +38,7 @@ /applications/system/storage_move_to_sd/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov /applications/system/js_app/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov @portasynthinca3 -/applications/debug/unit_tests/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov @gornekich @Astrrra @Skorpionm +/applications/debug/unit_tests/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov @gornekich @Skorpionm /applications/examples/example_thermo/ @skotopes @DrZlo13 @hedger @gsurkov @@ -49,7 +49,7 @@ /applications/main/infrared/resources/ @skotopes @DrZlo13 @hedger @gsurkov # Documentation -/documentation/ @skotopes @DrZlo13 @hedger @gsurkov +/documentation/ @skotopes @DrZlo13 @hedger @gsurkov @portasynthinca3 /scripts/toolchain/ @skotopes @DrZlo13 @hedger @gsurkov # Lib @@ -61,7 +61,7 @@ /lib/mbedtls/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov /lib/mjs/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov @portasynthinca3 /lib/nanopb/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov -/lib/nfc/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich @Astrrra +/lib/nfc/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich /lib/one_wire/ @skotopes @DrZlo13 @hedger @gsurkov /lib/subghz/ @skotopes @DrZlo13 @hedger @gsurkov @Skorpionm diff --git a/documentation/file_formats/SubGhzFileFormats.md b/documentation/file_formats/SubGhzFileFormats.md index c4d63835e..80047faf7 100644 --- a/documentation/file_formats/SubGhzFileFormats.md +++ b/documentation/file_formats/SubGhzFileFormats.md @@ -178,7 +178,7 @@ Data_RAW: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 DE 02 D3 54 D5 4C D2 C Frequency: 433920000 Preset: FuriHalSubGhzPresetCustom Custom_preset_module: CC1101 - Сustom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00 + Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00 Protocol: RAW RAW_Data: 29262 361 -68 2635 -66 24113 -66 11 ... RAW_Data: -424 205 -412 159 -412 381 -240 181 ... diff --git a/documentation/js/js_badusb.md b/documentation/js/js_badusb.md index d1d1e558d..79ae53f55 100644 --- a/documentation/js/js_badusb.md +++ b/documentation/js/js_badusb.md @@ -11,8 +11,9 @@ Start USB HID with optional parameters. Should be called before all other method Configuration object *(optional)*: - vid, pid (number): VID and PID values, both are mandatory -- mfr_name (string): Manufacturer name (32 ASCII characters max), optional -- prod_name (string): Product name (32 ASCII characters max), optional +- mfrName (string): Manufacturer name (32 ASCII characters max), optional +- prodName (string): Product name (32 ASCII characters max), optional +- layoutPath (string): Path to keyboard layout file, optional **Examples** ```js @@ -21,7 +22,7 @@ badusb.setup(); // Start USB HID with custom vid:pid = AAAA:BBBB, manufacturer and product strings not defined badusb.setup({ vid: 0xAAAA, pid: 0xBBBB }); // Start USB HID with custom vid:pid = AAAA:BBBB, manufacturer string = "Flipper Devices", product string = "Flipper Zero" -badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "Flipper Devices", prod_name: "Flipper Zero" }); +badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfrName: "Flipper Devices", prodName: "Flipper Zero" }); ```
@@ -122,6 +123,45 @@ badusb.println("Hello, world!"); // print "Hello, world!" and press "ENTER" ```
+## altPrint() +Prints a string by Alt+Numpad method - works only on Windows! + +**Parameters** + +- A string to print +- *(optional)* delay between key presses + +**Examples** +```js +badusb.altPrint("Hello, world!"); // print "Hello, world!" +badusb.altPrint("Hello, world!", 100); // Add 100ms delay between key presses +``` +
+ +## altPrintln() +Same as `altPrint` but ended with "ENTER" press. + +**Parameters** + +- A string to print +- *(optional)* delay between key presses + +**Examples** +```js +badusb.altPrintln("Hello, world!"); // print "Hello, world!" and press "ENTER" +``` +
+ +## quit() +Releases usb, optional, but allows to interchange with usbdisk. + +**Examples** +```js +badusb.quit(); +usbdisk.start(...) +``` +
+ # Key names list {#js_badusb_keynames} ## Modifier keys @@ -159,3 +199,4 @@ badusb.println("Hello, world!"); // print "Hello, world!" and press "ENTER" | TAB | | | MENU | Context menu key | | Fx | F1-F24 keys | +| NUMx | NUM0-NUM9 keys | diff --git a/documentation/js/js_builtin.md b/documentation/js/js_builtin.md index 0a128859a..2d2f9417a 100644 --- a/documentation/js/js_builtin.md +++ b/documentation/js/js_builtin.md @@ -39,28 +39,277 @@ print("string1", "string2", 123); ```
-## console.log() - -
- -## console.warn() - -
- -## console.error() - -
- -## console.debug() +## Console object Same as `print`, but output to serial console only, with corresponding log level. +### console.log() +
-## to_string() +### console.warn() + +
+ +### console.error() + +
+ +### console.debug() + +
+ +## load() +Runs a JS file and returns value from it. + +**Parameters** +- The path to the file +- An optional object to use as the global scope while running this file + +**Examples** +```js +load("/ext/apps/Scripts/script.js"); +``` +
+ +## chr() +Convert an ASCII character number to string. + +**Examples** +```js +chr(65); // "A" +``` +
+ +## die() +Exit JavaScript with given message. + +**Examples** +```js +die("Some error occurred"); +``` +
+ +## parseInt() +Convert a string to number with an optional base. + +**Examples** +```js +parseInt("123"); // 123 +parseInt("7b", 16); // 123 +``` +
+ +## Number object + +### Number.toString() Convert a number to string with an optional base. **Examples** ```js -to_string(123) // "123" -to_string(123, 16) // "0x7b" +let num = 123; +num.toString(); // "123" +num.toString(16); // "0x7b" +``` +
+ +## ArrayBuffer object + +**Fields** + +- byteLength: The length of the buffer in bytes +
+ +### ArrayBuffer.slice() +Creates an `ArrayBuffer` that contains a sub-part of the buffer. + +**Parameters** +- The index to start the new buffer at +- An optional non-inclusive index of where to stop the new buffer + +**Examples** +```js +Uint8Array([1, 2, 3]).buffer.slice(0, 1) // ArrayBuffer([1]) +``` +
+ +## DataView objects +Wrappers around `ArrayBuffer` objects, with dedicated types such as: +- `Uint8Array` +- `Int8Array` +- `Uint16Array` +- `Int16Array` +- `Uint32Array` +- `Int32Array` + +**Fields** + +- byteLength: The length of the buffer in bytes +- length: The length of the buffer in typed elements +- buffer: The underlying `ArrayBuffer` +
+ +## Array object + +**Fields** + +- length: How many elements there are in the array +
+ +### Array.splice() +Removes elements from the array and returns them in a new array. + +**Parameters** +- The index to start taking elements from +- An optional count of how many elements to take + +**Examples** +```js +let arr = [1, 2, 3]; +arr.splice(1); // [2, 3] +arr; // [1] +``` +
+ +### Array.push() +Adds a value to the end of the array. + +**Examples** +```js +let arr = [1, 2]; +arr.push(3); +arr; // [1, 2, 3] +``` +
+ +## String object + +**Fields** + +- length: How many characters there are in the string +
+ +### String.charCodeAt() +Returns the character code at an index in the string. + +**Examples** +```js +"A".charCodeAt(0) // 65 +``` +
+ +### String.at() +Same as `String.charCodeAt()`. +
+ +### String.indexOf() +Return index of first occurrence of substr within the string or `-1` if not found. + +**Parameters** +- Substring to search for +- Optional index to start searching from + +**Examples** +```js +"Example".indexOf("amp") // 2 +``` +
+ +### String.slice() +Return a substring between two indices. + +**Parameters** +- The index to start the new string at +- An optional non-inclusive index of where to stop the new string + +**Examples** +```js +"Example".slice(2) // "ample" +``` +
+ +### String.toUpperCase() +Transforms the string to upper case. + +**Examples** +```js +"Example".toUpperCase() // "EXAMPLE" +``` +
+ +### String.toLowerCase() +Transforms the string to lower case. + +**Examples** +```js +"Example".toLowerCase() // "example" +``` +
+ +## __dirname +Path to the directory containing the current script. + +**Examples** +```js +print(__dirname); // /ext/apps/Scripts +``` +
+ +## __filename +Path to the current script file. + +**Examples** +```js +print(__filename); // /ext/apps/Scripts/path.js +``` +
+ +# SDK compatibility methods {#js_builtin_sdk_compatibility} + +## sdkCompatibilityStatus() +Checks compatibility between the script and the JS SDK that the firmware provides. + +**Returns** +- `"compatible"` if the script and the JS SDK are compatible +- `"firmwareTooOld"` if the expected major version is larger than the version of the firmware, or if the expected minor version is larger than the version of the firmware +- `"firmwareTooNew"` if the expected major version is lower than the version of the firmware + +**Examples** +```js +sdkCompatibilityStatus(0, 3); // "compatible" +``` +
+ +## isSdkCompatible() +Checks compatibility between the script and the JS SDK that the firmware provides in a boolean fashion. + +**Examples** +```js +isSdkCompatible(0, 3); // true +``` +
+ +## checkSdkCompatibility() +Asks the user whether to continue executing the script if the versions are not compatible. Does nothing if they are. + +**Examples** +```js +checkSdkCompatibility(0, 3); +``` +
+ +## doesSdkSupport() +Checks whether all of the specified extra features are supported by the interpreter. + +**Examples** +```js +doesSdkSupport(["gui-widget"]); // true +``` +
+ +## checkSdkFeatures() +Checks whether all of the specified extra features are supported by the interpreter, asking the user if they want to continue running the script if they're not. + +**Examples** +```js +checkSdkFeatures(["gui-widget"]); ``` diff --git a/documentation/js/js_data_types.md b/documentation/js/js_data_types.md index bd3bb1f42..2a94ba5d2 100644 --- a/documentation/js/js_data_types.md +++ b/documentation/js/js_data_types.md @@ -7,7 +7,7 @@ Here is a list of common data types used by mJS. - foreign — C function or data pointer - undefined - null -- object — a data structure with named fields -- array — special type of object, all items have indexes and equal types +- Object — a data structure with named fields +- Array — special type of object, all items have indexes and equal types - ArrayBuffer — raw data buffer - DataView — provides interface for accessing ArrayBuffer contents diff --git a/documentation/js/js_developing_apps_using_js_sdk.md b/documentation/js/js_developing_apps_using_js_sdk.md index 952993566..1a764e1df 100644 --- a/documentation/js/js_developing_apps_using_js_sdk.md +++ b/documentation/js/js_developing_apps_using_js_sdk.md @@ -70,6 +70,27 @@ The JS minifier reduces the size of JavaScript files by removing unnecessary cha However, it has a drawback — it can make debugging harder, as error messages in minified files are harder to read in larger applications. For this reason, it's recommended to disable the JS minifier during debugging and it's disabled by default. To enable it, set the `minify` parameter to `true` in the `fz-sdk.config.json5` file in your app folder. This will minify your JavaScript app before loading it onto Flipper Zero. +## Differences with normal Flipper JavaScript + +With the Flipper JavaScript SDK, you will be developing in **TypeScript**. This means that you get a better development experience, with more accurate code completion and warnings when variable types are incompatible, but it also means your code will be different from basic Flipper JS. + +Some things to look out for: +- Importing modules: + - Instead of `let module = require("module");` + - You will use `import * as module from "@flipperdevices/fz-sdk/module";` +- Multiple source code files: + - The Flipper JavaScript SDK does not yet support having multiple `.ts` files and importing them + - You can use `load()`, but this will not benefit from TypeScript type checking +- Casting values: + - Some Flipper JavaScript functions will return generic types + - For example `eventLoop.subscribe()` will run your callback with a generic `Item` type + - In some cases you might need to cast these values before using them, you can do this by: + - Inline casting: `item` + - Declare with new type: `let text = item as string;` + +When you upload the script to Flipper with `npm start`, it gets transpiled to normal JavaScript and optionally minified (see below). If you're looking to share your script with others, this is what you should give them to run. + + ## What's next? You've learned how to run and debug simple JavaScript apps. But how can you access Flipper Zero's hardware from your JS code? For that, you'll need to use JS modules — which we'll cover in the next guide. diff --git a/documentation/js/js_flipper.md b/documentation/js/js_flipper.md new file mode 100644 index 000000000..a4da23868 --- /dev/null +++ b/documentation/js/js_flipper.md @@ -0,0 +1,52 @@ +# Flipper module {#js_flipper} + +The module contains methods and values to query device information and properties. Call the `require` function to load the module before first using its methods: + +```js +let flipper = require("flipper"); +``` + +# Values + +## firmwareVendor +String representing the firmware installed on the device. +Original firmware reports `"flipperdevices"`. +Do **NOT** use this to check the presence or absence of features, refer to [other ways to check SDK compatibility](#js_builtin_sdk_compatibility). + +## jsSdkVersion +Version of the JavaScript SDK. +Do **NOT** use this to check the presence or absence of features, refer to [other ways to check SDK compatibility](#js_builtin_sdk_compatibility). + +
+ +--- + +# Methods + +## getModel() +Returns the device model. + +**Example** +```js +flipper.getModel(); // "Flipper Zero" +``` + +
+ +## getName() +Returns the name of the virtual dolphin. + +**Example** +```js +flipper.getName(); // "Fur1pp44" +``` + +
+ +## getBatteryCharge() +Returns the battery charge percentage. + +**Example** +```js +flipper.getBatteryCharge(); // 100 +``` diff --git a/documentation/js/js_gpio.md b/documentation/js/js_gpio.md index 59a5504b0..d058d7329 100644 --- a/documentation/js/js_gpio.md +++ b/documentation/js/js_gpio.md @@ -24,7 +24,7 @@ delay(1000); --- # API reference -## get +## get() Gets a `Pin` object that can be used to manage a pin. **Parameters** @@ -89,3 +89,33 @@ Attaches an interrupt to a pin configured with `direction: "in"` and An event loop `Contract` object that identifies the interrupt event source. The event does not produce any extra data. + +### Pin.isPwmSupported() +Determines whether this pin supports PWM. +If `false`, all other PWM-related methods on this pin will throw an error when called. + +**Returns** + +Boolean value. + +### Pin.pwmWrite() +Sets PWM parameters and starts the PWM. +Configures the pin with `{ direction: "out", outMode: "push_pull" }`. +Throws an error if PWM is not supported on this pin. + +**Parameters** + - `freq`: Frequency in Hz + - `duty`: Duty cycle in % + +### Pin.isPwmRunning() +Determines whether PWM is running. +Throws an error if PWM is not supported on this pin. + +**Returns** + +Boolean value. + +### Pin.pwmStop() +Stops PWM. +Does not restore previous pin configuration. +Throws an error if PWM is not supported on this pin. diff --git a/documentation/js/js_gui.md b/documentation/js/js_gui.md index 2bb2a2eee..afca1434e 100644 --- a/documentation/js/js_gui.md +++ b/documentation/js/js_gui.md @@ -173,6 +173,11 @@ triggered when the back key is pressed.
+### viewDispatcher.currentView +The `View` object currently being shown. + +
+ ## ViewFactory When you import a module implementing a view, a `ViewFactory` is instantiated. For example, in the example above, `loadingView`, `submenuView` and `emptyView` are view factories. @@ -183,8 +188,40 @@ Creates an instance of a `View`.
-### ViewFactory.make(props) -Creates an instance of a `View` and assigns initial properties from `props`. +### ViewFactory.makeWith(props, children) +Creates an instance of a `View` and assigns initial properties from `props` and optionally a list of children. **Parameters** - `props`: simple key-value object, e.g. `{ header: "Header" }` + - `children`: optional array of children, e.g. `[ { element: "button", button: "right", text: "Back" } ]` + +## View +When you call `ViewFactory.make()` or `ViewFactory.makeWith()`, a `View` is instantiated. For example, in the example above, `views.loading`, `views.demos` and `views.empty` are views. + +
+ +### View.set(property, value) +Assign value to property by name. + +**Parameters** + - `property`: name of the property to change + - `value`: value to assign to the property + +
+ +### View.addChild(child) +Adds a child to the `View`. + +**Parameters** + - `child`: the child to add, e.g. `{ element: "button", button: "right", text: "Back" }` + +The format of the `child` parameter depends on the type of View that you're working with. Look in the View documentation. + +### View.resetChildren() +Removes all children from the `View`. + +### View.setChildren(children) +Removes all previous children from the `View` and assigns new children. + +**Parameters** + - `children`: the array of new children, e.g. `[ { element: "button", button: "right", text: "Back" } ]` diff --git a/documentation/js/js_gui__byte_input.md b/documentation/js/js_gui__byte_input.md new file mode 100644 index 000000000..abbf1ccad --- /dev/null +++ b/documentation/js/js_gui__byte_input.md @@ -0,0 +1,32 @@ +# Byte input GUI view {#js_gui__byte_input} + +Displays a hexadecimal keyboard. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let byteInputView = require("gui/byte_input"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they **must** be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +## Example +For an example refer to the `gui.js` example script. + +## View props + +| Prop | Type | Description | +|-------------|--------|--------------------------------------------------| +| `length` | `number` | The length in bytes of the buffer to modify. | +| `header` | `string` | A single line of text that appears above the keyboard. | +| `defaultData` | `string` | Data to show by default. | + +## View events + +| Item | Type | Description | +|-------------|--------|--------------------------------------------------| +| `input` | `ArrayBuffer` | Fires when the user selects the "Save" button. | diff --git a/documentation/js/js_gui__file_picker.md b/documentation/js/js_gui__file_picker.md new file mode 100644 index 000000000..a0854f0de --- /dev/null +++ b/documentation/js/js_gui__file_picker.md @@ -0,0 +1,20 @@ +# File Picker GUI prompt {#js_gui__file_picker} + +Allows asking the user to select a file. +It is not GUI view like other JS GUI views, rather just a function that shows a prompt. + +# Example +For an example, refer to the `gui.js` example script. + +# API reference + +## pickFile() +Displays a file picker and returns the selected file, or undefined if cancelled. + +**Parameters** + - `basePath`: the path to start at + - `extension`: the file extension(s) to show (like `.sub`, `.iso|.img`, `*`) + +**Returns** + +A `string` path, or `undefined`. diff --git a/documentation/js/js_gui__icon.md b/documentation/js/js_gui__icon.md new file mode 100644 index 000000000..2c6c4c5b7 --- /dev/null +++ b/documentation/js/js_gui__icon.md @@ -0,0 +1,32 @@ +# GUI Icons {#js_gui__icon} + +Retrieves and loads icons for use with GUI views such as [Dialog](#js_gui__dialog). + +# Example +For an example, refer to the `gui.js` example script. + +# API reference + +## getBuiltin() +Gets a built-in firmware icon by its name. +Not all icons are supported, currently only `"DolphinWait_59x54"` and `"js_script_10px"` are available. + +**Parameters** + - `icon`: name of the icon + +**Returns** + +An `IconData` object. + +
+ +## loadFxbm() +Loads a `.fxbm` icon (XBM Flipper sprite, from `flipperzero-game-engine`) from file. +It will be automatically unloaded when the script exits. + +**Parameters** + - `path`: path to the `.fxbm` file + +**Returns** + +An `IconData` object. diff --git a/documentation/js/js_gui__text_box.md b/documentation/js/js_gui__text_box.md index cdbeec7b7..4c94b8c3c 100644 --- a/documentation/js/js_gui__text_box.md +++ b/documentation/js/js_gui__text_box.md @@ -21,4 +21,6 @@ For an example, refer to the `gui.js` example script. | Prop | Type | Description | |----------|---------|------------------------------------| -| `text` | `string`| Text to show in the text box. | +| `text` | `string`| Text to show in the text box. | +| `font` | `string`| The font to display the text in (`"text"` or `"hex"`). | +| `focus` | `string`| The initial focus of the text box (`"start"` or `"end"`). | diff --git a/documentation/js/js_gui__text_input.md b/documentation/js/js_gui__text_input.md index 3b8aafaad..c63297c12 100644 --- a/documentation/js/js_gui__text_input.md +++ b/documentation/js/js_gui__text_input.md @@ -21,9 +21,11 @@ For an example, refer to the `gui.js` example script. | Prop | Type | Description | |-------------|--------|--------------------------------------------------| -| `minLength` | `number` | The shortest allowed text length. | -| `maxLength` | `number` | The longest allowed text length.
Default: `32` | -| `header` | `string` | A single line of text that appears above the keyboard. | +| `minLength` | `number` | The shortest allowed text length. | +| `maxLength` | `number` | The longest allowed text length.
Default: `32` | +| `header` | `string` | A single line of text that appears above the keyboard. | +| `defaultText` | `string` | Text to show by default. | +| `defaultTextClear` | `boolean` | Whether to clear the default text on next character typed. | ## View events diff --git a/documentation/js/js_gui__widget.md b/documentation/js/js_gui__widget.md index 2c31327d2..9ea3e4dfa 100644 --- a/documentation/js/js_gui__widget.md +++ b/documentation/js/js_gui__widget.md @@ -22,3 +22,16 @@ This view does not have any props. ## Children This view has the elements as its children. +Elements are objects with properties to define them, in the form `{ element: "type", ...properties }` (e.g. `{ element: "button", button: "right", text: "Back" }`). + +| **Element Type** | **Properties** | **Description** | +|------------------|----------------|------------------------------------------------| +| `string_multiline` | `x` (number), `y` (number)
`align` ((`"t"`, `"c"`, `"b"`) + (`"l"`, `"m"`, `"r"`))
`font` (`"primary"`, `"secondary"`, `"keyboard"`, `"big_numbers"`)
`text` (string) | String of text that can span multiple lines. | +| `string` | `x` (number), `y` (number)
`align` ((`"t"`, `"c"`, `"b"`) + (`"l"`, `"m"`, `"r"`))
`font` (`"primary"`, `"secondary"`, `"keyboard"`, `"big_numbers"`)
`text` (string) | String of text. | +| `text_box` | `x` (number), `y` (number)
`w` (number), `h` (number)
`align` ((`"t"`, `"c"`, `"b"`) + (`"l"`, `"m"`, `"r"`))
`text` (string)
`stripToDots` (boolean) | Box of with text that can be scrolled vertically. | +| `text_scroll` | `x` (number), `y` (number)
`w` (number), `h` (number)
`text` (string) | Text that can be scrolled vertically. | +| `button` | `text` (string)
`button` (`"left"`, `"center"`, `"right"`) | Button at the bottom of the screen. | +| `icon` | `x` (number), `y` (number)
`iconData` ([IconData](#js_gui__icon)) | Display an icon. | +| `rect` | `x` (number), `y` (number)
`w` (number), `h` (number)
`radius` (number), `fill` (boolean) | Draw a rectangle, optionally rounded and filled. | +| `circle` | `x` (number), `y` (number)
`radius` (number), `fill` (boolean) | Draw a circle, optionally filled. | +| `line` | `x1` (number), `y1` (number)
`x2` (number), `y2` (number) | Draw a line between 2 points. | diff --git a/documentation/js/js_math.md b/documentation/js/js_math.md index 78357bbd7..fd7bede17 100644 --- a/documentation/js/js_math.md +++ b/documentation/js/js_math.md @@ -271,13 +271,29 @@ math.floor(45.95); // 45
+## log() +Return the natural logarithm of x. + +**Parameters** +- x: A number + +**Returns** + +The natural logarithm of `x`, as in `ln(x)` where `e` is the base of the natural logarithm. + +**Example** +```js +math.log(1); // 0 +math.log(3); // 1.0986122886681098 +``` + ## isEqual() -Return true if the difference between numbers `a` and `b` is less than the specified parameter `e`. +Return true if the difference between numbers `a` and `b` is less than the specified `tolerance`. **Parameters** - a: A number a - b: A number b -- e: An epsilon parameter +- tolerance: How much difference is allowed between the numbers to be considered equal **Returns** diff --git a/documentation/js/js_serial.md b/documentation/js/js_serial.md index 01a72b97b..d06c799ac 100644 --- a/documentation/js/js_serial.md +++ b/documentation/js/js_serial.md @@ -10,8 +10,15 @@ Configure serial port. Should be called before all other methods. **Parameters** -- Serial port name (usart, lpuart) +- Serial port name (`"usart"`, `"lpuart"`) - Baudrate +- Optional framing configuration object (e.g. `{ dataBits: "8", parity: "even", stopBits: "1" }`): + - `dataBits`: `"6"`, `"7"`, `"8"`, `"9"` + - 6 data bits can only be selected when parity is enabled (even or odd) + - 9 data bits can only be selected when parity is disabled (none) + - `parity`: `"none"`, `"even"`, `"odd"` + - `stopBits`: `"0.5"`, `"1"`, `"1.5"`, `"2"` + - LPUART only supports whole stop bit lengths (i.e. 1 and 2 but not 0.5 and 1.5) **Example** @@ -69,7 +76,7 @@ Read from serial port until line break character. **Parameters** -*(optional)* Timeout value in ms. +- *(optional)* Timeout value in ms. **Returns** @@ -84,6 +91,26 @@ serial.readln(5000); // Read with 5s timeout
+## readAny() +Read any available amount of data from serial port, can help avoid starving your loop with small reads. + +**Parameters** + +- *(optional)* Timeout value in ms + +**Returns** + +A sting of received characters or undefined if nothing was received before timeout. + +**Example** + +```js +serial.readAny(); // Read without timeout +serial.readAny(5000); // Read with 5s timeout +``` + +
+ ## readBytes() Read from serial port until line break character. @@ -125,8 +152,21 @@ Index of matched pattern in input patterns list, undefined if nothing was found. ```js // Wait for root shell prompt with 1s timeout, returns 0 if it was received before timeout, undefined if not -serial.expect("# ", 1000); +serial.expect("# ", 1000); // Infinitely wait for one of two strings, should return 0 if the first string got matched, 1 if the second one serial.expect([": not found", "Usage: "]); +``` + +
+ +## end() +Deinitializes serial port, allowing multiple initializations per script run. + +**Example** + +```js +serial.end(); +// Configure LPUART port with baudrate = 115200 +serial.setup("lpuart", 115200); ``` \ No newline at end of file diff --git a/documentation/js/js_storage.md b/documentation/js/js_storage.md index acfa4e331..0e435be5b 100644 --- a/documentation/js/js_storage.md +++ b/documentation/js/js_storage.md @@ -52,7 +52,7 @@ File information structure. ## File -This class implements methods for working with file. To get an object of the File class, use the `OpenFile` method. +This class implements methods for working with file. To get an object of the File class, use the `openFile` method.
@@ -186,6 +186,36 @@ Copies bytes from the R/W pointer in the current file to the R/W pointer in anot # Methods +## openFile() + +Opens a file. + +**Parameters** + +- path: The path to the file +- accessMode: How to access the file (`"r"`, `"w"` or `"rw"`) +- openMode: How to open the file (`"open_existing"`, `"open_always"`, `"open_append"`, `"create_new"` or `"create_always"`) + +**Returns** + +A `File` object on success, `undefined` otherwise. + +
+ +## fileExists() + +Detects whether a file exists. + +**Parameters** + +- path: The path to the file + +**Returns** + +`true` if file exists, `false` otherwise. + +
+ ## arePathsEqual() Determines whether the two paths are equivalent. Respects filesystem-defined path equivalence rules. diff --git a/lib/mjs/mjs_primitive.c b/lib/mjs/mjs_primitive.c index e73ae892d..20d2a8c66 100644 --- a/lib/mjs/mjs_primitive.c +++ b/lib/mjs/mjs_primitive.c @@ -9,6 +9,8 @@ #include "mjs_string_public.h" #include "mjs_util.h" +#include + mjs_val_t mjs_mk_null(void) { return MJS_NULL; } @@ -94,7 +96,7 @@ int mjs_is_boolean(mjs_val_t v) { } #define MJS_IS_POINTER_LEGIT(n) \ - (((n)&MJS_TAG_MASK) == 0 || ((n)&MJS_TAG_MASK) == (~0 & MJS_TAG_MASK)) + (((n) & MJS_TAG_MASK) == 0 || ((n) & MJS_TAG_MASK) == (~0 & MJS_TAG_MASK)) MJS_PRIVATE mjs_val_t mjs_pointer_to_value(struct mjs* mjs, void* p) { uint64_t n = ((uint64_t)(uintptr_t)p); @@ -165,13 +167,13 @@ MJS_PRIVATE void mjs_number_to_string(struct mjs* mjs) { mjs_val_t ret = MJS_UNDEFINED; mjs_val_t base_v = MJS_UNDEFINED; int32_t base = 10; - int32_t num; + double num; /* get number from `this` */ if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_NUMBER, NULL)) { goto clean; } - num = mjs_get_int32(mjs, mjs->vals.this_obj); + num = mjs_get_double(mjs, mjs->vals.this_obj); if(mjs_nargs(mjs) >= 1) { /* get base from arg 0 */ @@ -181,9 +183,20 @@ MJS_PRIVATE void mjs_number_to_string(struct mjs* mjs) { base = mjs_get_int(mjs, base_v); } - char tmp_str[] = "-2147483648"; - itoa(num, tmp_str, base); - ret = mjs_mk_string(mjs, tmp_str, ~0, true); + if(base != 10 || floor(num) == num) { + char tmp_str[] = "-2147483648"; + itoa((int32_t)num, tmp_str, base); + ret = mjs_mk_string(mjs, tmp_str, ~0, true); + } else { + char tmp_str[] = "2.22514337200000e-308"; + snprintf(tmp_str, sizeof(tmp_str), "%.*g", DBL_DIG, num); + size_t len = strlen(tmp_str); + while(len && tmp_str[len - 1] == '0') { + len--; + } + tmp_str[len] = '\0'; + ret = mjs_mk_string(mjs, tmp_str, ~0, true); + } clean: mjs_return(mjs, ret);