mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-12 20:18:35 -07:00
Merge branch 'dev' of https://github.com/flipperdevices/flipperzero-firmware into xfw-dev
This commit is contained in:
@@ -86,7 +86,10 @@ Small applications providing configuration for basic firmware and its services.
|
||||
|
||||
## system
|
||||
|
||||
Utility apps not visible in other menus.
|
||||
Utility apps not visible in other menus, plus few external apps pre-packaged with the firmware.
|
||||
|
||||
- `hid_app` - BLE & USB HID remote
|
||||
- `js_app` - JS engine runner
|
||||
- `snake_game` - Snake game
|
||||
- `storage_move_to_sd` - Data migration tool for internal storage
|
||||
- `updater` - Update service & application
|
||||
|
||||
@@ -34,6 +34,7 @@ static const char* known_ext[] = {
|
||||
[ArchiveFileTypeBadKb] = ".txt",
|
||||
[ArchiveFileTypeU2f] = "?",
|
||||
[ArchiveFileTypeApplication] = ".fap",
|
||||
[ArchiveFileTypeJS] = ".js",
|
||||
[ArchiveFileTypeSearch] = "*",
|
||||
[ArchiveFileTypeUpdateManifest] = ".fuf",
|
||||
[ArchiveFileTypeFolder] = "?",
|
||||
|
||||
@@ -21,6 +21,7 @@ typedef enum {
|
||||
ArchiveFileTypeBadKb,
|
||||
ArchiveFileTypeU2f,
|
||||
ArchiveFileTypeApplication,
|
||||
ArchiveFileTypeJS,
|
||||
ArchiveFileTypeSearch,
|
||||
ArchiveFileTypeUpdateManifest,
|
||||
ArchiveFileTypeFolder,
|
||||
|
||||
@@ -36,6 +36,8 @@ const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) {
|
||||
return "U2F";
|
||||
case ArchiveFileTypeUpdateManifest:
|
||||
return "UpdaterApp";
|
||||
case ArchiveFileTypeJS:
|
||||
return "JS Runner";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ static const Icon* ArchiveItemIcons[] = {
|
||||
[ArchiveFileTypeBadKb] = &I_badkb_10px,
|
||||
[ArchiveFileTypeU2f] = &I_u2f_10px,
|
||||
[ArchiveFileTypeApplication] = &I_Apps_10px,
|
||||
[ArchiveFileTypeJS] = &I_js_script_10px,
|
||||
[ArchiveFileTypeSearch] = &I_search_10px,
|
||||
[ArchiveFileTypeUpdateManifest] = &I_update_10px,
|
||||
[ArchiveFileTypeFolder] = &I_dir_10px,
|
||||
|
||||
@@ -32,12 +32,12 @@ typedef enum {
|
||||
(WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit | \
|
||||
WorkerEvtFolderRefresh | WorkerEvtConfigChange)
|
||||
|
||||
ARRAY_DEF(_idx_last_array, int32_t) // Unused, kept for compatibility
|
||||
ARRAY_DEF(_IdxLastArray, int32_t) // Unused, kept for compatibility
|
||||
ARRAY_DEF(ExtFilterArray, FuriString*, FURI_STRING_OPLIST)
|
||||
|
||||
struct BrowserWorker {
|
||||
FuriThread* thread;
|
||||
|
||||
FuriString* filter_extension;
|
||||
FuriString* path_start;
|
||||
FuriString* path_current;
|
||||
FuriString* path_next;
|
||||
@@ -46,7 +46,8 @@ struct BrowserWorker {
|
||||
uint32_t load_count;
|
||||
bool skip_assets;
|
||||
bool hide_dot_files;
|
||||
_idx_last_array_t _idx_last; // Unused, kept for compatibility
|
||||
_IdxLastArray_t _idx_last; // Unused, kept for compatibility
|
||||
ExtFilterArray_t ext_filter;
|
||||
|
||||
void* cb_ctx;
|
||||
BrowserWorkerFolderOpenCallback folder_cb;
|
||||
@@ -55,6 +56,7 @@ struct BrowserWorker {
|
||||
BrowserWorkerLongLoadCallback long_load_cb;
|
||||
|
||||
bool keep_selection;
|
||||
FuriString* passed_ext_filter;
|
||||
};
|
||||
|
||||
static bool browser_path_is_file(FuriString* path) {
|
||||
@@ -80,6 +82,31 @@ static bool browser_path_trim(FuriString* path) {
|
||||
}
|
||||
return is_root;
|
||||
}
|
||||
static void browser_parse_ext_filter(ExtFilterArray_t ext_filter, const char* filter_str) {
|
||||
if(!filter_str) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(filter_str);
|
||||
if(len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t str_offset = 0;
|
||||
FuriString* ext_temp = furi_string_alloc();
|
||||
while(1) {
|
||||
size_t ext_len = strcspn(&filter_str[str_offset], "|");
|
||||
|
||||
furi_string_set_strn(ext_temp, &filter_str[str_offset], ext_len);
|
||||
ExtFilterArray_push_back(ext_filter, ext_temp);
|
||||
|
||||
str_offset += ext_len + 1;
|
||||
if(str_offset >= len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
furi_string_free(ext_temp);
|
||||
}
|
||||
|
||||
static bool browser_filter_by_name(BrowserWorker* browser, FuriString* name, bool is_folder) {
|
||||
// Skip dot files if enabled
|
||||
@@ -98,12 +125,20 @@ static bool browser_filter_by_name(BrowserWorker* browser, FuriString* name, boo
|
||||
}
|
||||
} else {
|
||||
// Filter files by extension
|
||||
if((furi_string_empty(browser->filter_extension)) ||
|
||||
(furi_string_cmp_str(browser->filter_extension, "*") == 0)) {
|
||||
if(ExtFilterArray_size(browser->ext_filter) == 0) {
|
||||
return true;
|
||||
}
|
||||
if(furi_string_end_with(name, browser->filter_extension)) {
|
||||
return true;
|
||||
|
||||
ExtFilterArray_it_t it;
|
||||
for(ExtFilterArray_it(it, browser->ext_filter); !ExtFilterArray_end_p(it);
|
||||
ExtFilterArray_next(it)) {
|
||||
FuriString* ext = *ExtFilterArray_cref(it);
|
||||
if((furi_string_empty(ext)) || (furi_string_cmp_str(ext, "*") == 0)) {
|
||||
return true;
|
||||
}
|
||||
if(furi_string_end_with(name, ext)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -460,14 +495,17 @@ static int32_t browser_worker(void* context) {
|
||||
BrowserWorker* file_browser_worker_alloc(
|
||||
FuriString* path,
|
||||
const char* base_path,
|
||||
const char* filter_ext,
|
||||
const char* ext_filter,
|
||||
bool skip_assets,
|
||||
bool hide_dot_files) {
|
||||
BrowserWorker* browser = malloc(sizeof(BrowserWorker));
|
||||
|
||||
browser->filter_extension = furi_string_alloc_set(filter_ext);
|
||||
ExtFilterArray_init(browser->ext_filter);
|
||||
|
||||
browser_parse_ext_filter(browser->ext_filter, ext_filter);
|
||||
browser->skip_assets = skip_assets;
|
||||
browser->hide_dot_files = hide_dot_files;
|
||||
browser->passed_ext_filter = furi_string_alloc_set(ext_filter);
|
||||
|
||||
browser->path_current = furi_string_alloc_set(path);
|
||||
browser->path_next = furi_string_alloc_set(path);
|
||||
@@ -490,10 +528,12 @@ void file_browser_worker_free(BrowserWorker* browser) {
|
||||
furi_thread_join(browser->thread);
|
||||
furi_thread_free(browser->thread);
|
||||
|
||||
furi_string_free(browser->filter_extension);
|
||||
furi_string_free(browser->path_next);
|
||||
furi_string_free(browser->path_current);
|
||||
furi_string_free(browser->path_start);
|
||||
furi_string_free(browser->passed_ext_filter);
|
||||
|
||||
ExtFilterArray_clear(browser->ext_filter);
|
||||
|
||||
free(browser);
|
||||
}
|
||||
@@ -534,13 +574,13 @@ void file_browser_worker_set_long_load_callback(
|
||||
void file_browser_worker_set_config(
|
||||
BrowserWorker* browser,
|
||||
FuriString* path,
|
||||
const char* filter_ext,
|
||||
const char* ext_filter,
|
||||
bool skip_assets,
|
||||
bool hide_dot_files) {
|
||||
furi_assert(browser);
|
||||
furi_string_set(browser->path_next, path);
|
||||
browser->keep_selection = false;
|
||||
furi_string_set(browser->filter_extension, filter_ext);
|
||||
browser_parse_ext_filter(browser->ext_filter, ext_filter);
|
||||
browser->skip_assets = skip_assets;
|
||||
browser->hide_dot_files = hide_dot_files;
|
||||
furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtConfigChange);
|
||||
@@ -548,7 +588,7 @@ void file_browser_worker_set_config(
|
||||
|
||||
const char* file_browser_worker_get_filter_ext(BrowserWorker* browser) {
|
||||
furi_assert(browser);
|
||||
return furi_string_get_cstr(browser->filter_extension);
|
||||
return furi_string_get_cstr(browser->passed_ext_filter);
|
||||
}
|
||||
|
||||
void file_browser_worker_set_filter_ext(
|
||||
@@ -558,7 +598,7 @@ void file_browser_worker_set_filter_ext(
|
||||
furi_assert(browser);
|
||||
furi_string_set(browser->path_next, path);
|
||||
browser->keep_selection = true;
|
||||
furi_string_set(browser->filter_extension, filter_ext);
|
||||
furi_string_set(browser->passed_ext_filter, filter_ext);
|
||||
furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtConfigChange);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ typedef void (*BrowserWorkerLongLoadCallback)(void* context);
|
||||
BrowserWorker* file_browser_worker_alloc(
|
||||
FuriString* path,
|
||||
const char* base_path,
|
||||
const char* filter_ext,
|
||||
const char* ext_filter,
|
||||
bool skip_assets,
|
||||
bool hide_dot_files);
|
||||
|
||||
@@ -53,7 +53,7 @@ void file_browser_worker_set_long_load_callback(
|
||||
void file_browser_worker_set_config(
|
||||
BrowserWorker* browser,
|
||||
FuriString* path,
|
||||
const char* filter_ext,
|
||||
const char* ext_filter,
|
||||
bool skip_assets,
|
||||
bool hide_dot_files);
|
||||
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
#include <gui/view_holder.h>
|
||||
#include <gui/modules/loading.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
|
||||
#define TAG "LoaderApplications"
|
||||
|
||||
#define JS_RUNNER_APP "JS Runner"
|
||||
|
||||
struct LoaderApplications {
|
||||
FuriThread* thread;
|
||||
void (*closed_cb)(void*);
|
||||
@@ -36,7 +39,7 @@ void loader_applications_free(LoaderApplications* loader_applications) {
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
FuriString* fap_path;
|
||||
FuriString* file_path;
|
||||
DialogsApp* dialogs;
|
||||
Storage* storage;
|
||||
Loader* loader;
|
||||
@@ -48,7 +51,7 @@ typedef struct {
|
||||
|
||||
static LoaderApplicationsApp* loader_applications_app_alloc() {
|
||||
LoaderApplicationsApp* app = malloc(sizeof(LoaderApplicationsApp)); //-V799
|
||||
app->fap_path = furi_string_alloc_set(EXT_PATH("apps"));
|
||||
app->file_path = furi_string_alloc_set(EXT_PATH("apps"));
|
||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
app->storage = furi_record_open(RECORD_STORAGE);
|
||||
app->loader = furi_record_open(RECORD_LOADER);
|
||||
@@ -73,7 +76,7 @@ static void loader_applications_app_free(LoaderApplicationsApp* app) {
|
||||
furi_record_close(RECORD_LOADER);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
furi_string_free(app->fap_path);
|
||||
furi_string_free(app->file_path);
|
||||
free(app);
|
||||
}
|
||||
|
||||
@@ -84,13 +87,19 @@ static bool loader_applications_item_callback(
|
||||
FuriString* item_name) {
|
||||
LoaderApplicationsApp* loader_applications_app = context;
|
||||
furi_assert(loader_applications_app);
|
||||
return flipper_application_load_name_and_icon(
|
||||
path, loader_applications_app->storage, icon_ptr, item_name);
|
||||
if(furi_string_end_with(path, ".fap")) {
|
||||
return flipper_application_load_name_and_icon(
|
||||
path, loader_applications_app->storage, icon_ptr, item_name);
|
||||
} else {
|
||||
path_extract_filename(path, item_name, false);
|
||||
memcpy(*icon_ptr, icon_get_data(&I_js_script_10px), FAP_MANIFEST_MAX_ICON_SIZE);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static bool loader_applications_select_app(LoaderApplicationsApp* loader_applications_app) {
|
||||
const DialogsFileBrowserOptions browser_options = {
|
||||
.extension = ".fap",
|
||||
.extension = ".fap|.js",
|
||||
.skip_assets = true,
|
||||
.icon = &I_unknown_10px,
|
||||
.hide_ext = true,
|
||||
@@ -101,8 +110,8 @@ static bool loader_applications_select_app(LoaderApplicationsApp* loader_applica
|
||||
|
||||
return dialog_file_browser_show(
|
||||
loader_applications_app->dialogs,
|
||||
loader_applications_app->fap_path,
|
||||
loader_applications_app->fap_path,
|
||||
loader_applications_app->file_path,
|
||||
loader_applications_app->file_path,
|
||||
&browser_options);
|
||||
}
|
||||
|
||||
@@ -117,11 +126,10 @@ static void loader_pubsub_callback(const void* message, void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
static void loader_applications_start_app(LoaderApplicationsApp* app) {
|
||||
const char* name = furi_string_get_cstr(app->fap_path);
|
||||
|
||||
if(!furi_string_start_with_str(app->fap_path, EXT_PATH("apps/Games/")) &&
|
||||
!furi_string_start_with_str(app->fap_path, EXT_PATH("apps/Media/"))) {
|
||||
static void
|
||||
loader_applications_start_app(LoaderApplicationsApp* app, const char* name, const char* args) {
|
||||
if(!furi_string_start_with_str(app->file_path, EXT_PATH("apps/Games/")) &&
|
||||
!furi_string_start_with_str(app->file_path, EXT_PATH("apps/Media/"))) {
|
||||
dolphin_deed(DolphinDeedPluginInternalStart);
|
||||
}
|
||||
|
||||
@@ -130,7 +138,7 @@ static void loader_applications_start_app(LoaderApplicationsApp* app) {
|
||||
FuriPubSubSubscription* subscription =
|
||||
furi_pubsub_subscribe(loader_get_pubsub(app->loader), loader_pubsub_callback, thread_id);
|
||||
|
||||
LoaderStatus status = loader_start_with_gui_error(app->loader, name, NULL);
|
||||
LoaderStatus status = loader_start_with_gui_error(app->loader, name, args);
|
||||
|
||||
if(status == LoaderStatusOk) {
|
||||
furi_thread_flags_wait(APPLICATION_STOP_EVENT, FuriFlagWaitAny, FuriWaitForever);
|
||||
@@ -147,7 +155,12 @@ static int32_t loader_applications_thread(void* p) {
|
||||
view_holder_start(app->view_holder);
|
||||
|
||||
while(loader_applications_select_app(app)) {
|
||||
loader_applications_start_app(app);
|
||||
if(!furi_string_end_with(app->file_path, ".js")) {
|
||||
loader_applications_start_app(app, furi_string_get_cstr(app->file_path), NULL);
|
||||
} else {
|
||||
loader_applications_start_app(
|
||||
app, JS_RUNNER_APP, furi_string_get_cstr(app->file_path));
|
||||
}
|
||||
}
|
||||
|
||||
// stop loading animation
|
||||
|
||||
@@ -5,6 +5,7 @@ App(
|
||||
provides=[
|
||||
"updater_app",
|
||||
"storage_move_to_sd",
|
||||
"js_app",
|
||||
# "archive",
|
||||
],
|
||||
)
|
||||
|
||||
41
applications/system/js_app/application.fam
Normal file
41
applications/system/js_app/application.fam
Normal file
@@ -0,0 +1,41 @@
|
||||
App(
|
||||
appid="js_app",
|
||||
name="JS Runner",
|
||||
apptype=FlipperAppType.SYSTEM,
|
||||
entry_point="js_app",
|
||||
stack_size=2 * 1024,
|
||||
resources="examples",
|
||||
order=0,
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_dialog",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_dialog_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_dialog.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_notification",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_notification_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_notification.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_badusb",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_badusb_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_badusb.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_uart",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_uart_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_uart.c"],
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
let arr_1 = Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
print("len =", arr_1.buffer.byteLength);
|
||||
|
||||
let arr_2 = Uint8Array(arr_1.buffer.slice(2, 6));
|
||||
print("slice len =", arr_2.buffer.byteLength);
|
||||
for (let i = 0; i < arr_2.buffer.byteLength; i++) {
|
||||
print(arr_2[i]);
|
||||
}
|
||||
20
applications/system/js_app/examples/apps/Scripts/bad_uart.js
Normal file
20
applications/system/js_app/examples/apps/Scripts/bad_uart.js
Normal file
@@ -0,0 +1,20 @@
|
||||
let uart = require("uart");
|
||||
uart.setup(115200);
|
||||
|
||||
// uart.write("\n");
|
||||
uart.write([0x0a]);
|
||||
let console_resp = uart.expect("# ", 1000);
|
||||
if (console_resp === undefined) {
|
||||
print("No CLI response");
|
||||
} else {
|
||||
uart.write("uci\n");
|
||||
let uci_state = uart.expect([": not found", "Usage: "]);
|
||||
if (uci_state === 1) {
|
||||
uart.expect("# ");
|
||||
uart.write("uci show wireless\n");
|
||||
uart.expect(".key=");
|
||||
print("key:", uart.readln());
|
||||
} else {
|
||||
print("uci cmd not found");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
let badusb = require("badusb");
|
||||
let notify = require("notification");
|
||||
let flipper = require("flipper");
|
||||
let dialog = require("dialog");
|
||||
|
||||
badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "Flipper", prod_name: "Zero" });
|
||||
dialog.message("BadUSB demo", "Press OK to start");
|
||||
|
||||
if (badusb.isConnected()) {
|
||||
notify.blink("green", "short");
|
||||
print("USB is connected");
|
||||
|
||||
badusb.println("Hello, world!");
|
||||
|
||||
badusb.press("CTRL", "a");
|
||||
badusb.press("CTRL", "c");
|
||||
badusb.press("DOWN");
|
||||
delay(1000);
|
||||
badusb.press("CTRL", "v");
|
||||
delay(1000);
|
||||
badusb.press("CTRL", "v");
|
||||
|
||||
badusb.println("1234", 200);
|
||||
|
||||
badusb.println("Flipper Model: " + flipper.getModel());
|
||||
badusb.println("Flipper Name: " + flipper.getName());
|
||||
badusb.println("Battery level: " + to_string(flipper.getBatteryCharge()) + "%");
|
||||
|
||||
notify.success();
|
||||
} else {
|
||||
print("USB not connected");
|
||||
notify.error();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
print("print", 1);
|
||||
console.log("log", 2);
|
||||
console.warn("warn", 3);
|
||||
console.error("error", 4);
|
||||
console.debug("debug", 5);
|
||||
@@ -0,0 +1,9 @@
|
||||
print("start");
|
||||
delay(1000)
|
||||
print("1");
|
||||
delay(1000)
|
||||
print("2");
|
||||
delay(1000)
|
||||
print("3");
|
||||
delay(1000)
|
||||
print("end");
|
||||
19
applications/system/js_app/examples/apps/Scripts/dialog.js
Normal file
19
applications/system/js_app/examples/apps/Scripts/dialog.js
Normal file
@@ -0,0 +1,19 @@
|
||||
let dialog = require("dialog");
|
||||
|
||||
let result1 = dialog.message("Dialog demo", "Press OK to start");
|
||||
print(result1);
|
||||
|
||||
let dialog_params = ({
|
||||
header: "Test_header",
|
||||
text: "Test_text",
|
||||
button_left: "Left",
|
||||
button_right: "Right",
|
||||
button_center: "OK"
|
||||
});
|
||||
|
||||
let result2 = dialog.custom(dialog_params);
|
||||
if (result2 === "") {
|
||||
print("Back is pressed");
|
||||
} else {
|
||||
print(result2, "is pressed");
|
||||
}
|
||||
3
applications/system/js_app/examples/apps/Scripts/load.js
Normal file
3
applications/system/js_app/examples/apps/Scripts/load.js
Normal file
@@ -0,0 +1,3 @@
|
||||
let math = load("/ext/apps/Scripts/load_api.js");
|
||||
let result = math.add(5, 10);
|
||||
print(result);
|
||||
@@ -0,0 +1,3 @@
|
||||
({
|
||||
add: function (a, b) { return a + b; },
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
let notify = require("notification");
|
||||
notify.error();
|
||||
delay(1000);
|
||||
notify.success();
|
||||
delay(1000);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
notify.blink("red", "short");
|
||||
delay(500);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
let uart = require("uart");
|
||||
uart.setup(115200);
|
||||
|
||||
while (1) {
|
||||
let rx_data = uart.readBytes(1, 0);
|
||||
if (rx_data !== undefined) {
|
||||
uart.write(rx_data);
|
||||
let data_view = Uint8Array(rx_data);
|
||||
print("0x" + to_hex_string(data_view[0]));
|
||||
}
|
||||
}
|
||||
BIN
applications/system/js_app/icon.png
Normal file
BIN
applications/system/js_app/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
131
applications/system/js_app/js_app.c
Normal file
131
applications/system/js_app/js_app.c
Normal file
@@ -0,0 +1,131 @@
|
||||
#include <dialogs/dialogs.h>
|
||||
#include "js_thread.h"
|
||||
#include <storage/storage.h>
|
||||
#include "js_app_i.h"
|
||||
#include <toolbox/path.h>
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define TAG "JS app"
|
||||
|
||||
typedef struct {
|
||||
JsThread* js_thread;
|
||||
Gui* gui;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Loading* loading;
|
||||
JsConsoleView* console_view;
|
||||
} JsApp;
|
||||
|
||||
static uint32_t js_view_exit(void* context) {
|
||||
UNUSED(context);
|
||||
return VIEW_NONE;
|
||||
}
|
||||
|
||||
static void js_app_compact_trace(FuriString* trace_str) {
|
||||
// Keep only first line
|
||||
size_t line_end = furi_string_search_char(trace_str, '\n');
|
||||
if(line_end > 0) {
|
||||
furi_string_left(trace_str, line_end);
|
||||
}
|
||||
|
||||
// Remove full path
|
||||
FuriString* file_name = furi_string_alloc();
|
||||
size_t filename_start = furi_string_search_rchar(trace_str, '/');
|
||||
if(filename_start > 0) {
|
||||
filename_start++;
|
||||
furi_string_set_n(
|
||||
file_name, trace_str, filename_start, furi_string_size(trace_str) - filename_start);
|
||||
furi_string_printf(trace_str, "at %s", furi_string_get_cstr(file_name));
|
||||
}
|
||||
|
||||
furi_string_free(file_name);
|
||||
}
|
||||
|
||||
static void js_callback(JsThreadEvent event, const char* msg, void* context) {
|
||||
JsApp* app = context;
|
||||
furi_assert(app);
|
||||
|
||||
if(event == JsThreadEventDone) {
|
||||
FURI_LOG_I(TAG, "Script done");
|
||||
console_view_print(app->console_view, "--- DONE ---");
|
||||
} else if(event == JsThreadEventPrint) {
|
||||
console_view_print(app->console_view, msg);
|
||||
} else if(event == JsThreadEventError) {
|
||||
console_view_print(app->console_view, "--- ERROR ---");
|
||||
console_view_print(app->console_view, msg);
|
||||
} else if(event == JsThreadEventErrorTrace) {
|
||||
FuriString* compact_trace = furi_string_alloc_set_str(msg);
|
||||
js_app_compact_trace(compact_trace);
|
||||
console_view_print(app->console_view, furi_string_get_cstr(compact_trace));
|
||||
furi_string_free(compact_trace);
|
||||
console_view_print(app->console_view, "See logs for full trace");
|
||||
}
|
||||
}
|
||||
|
||||
static JsApp* js_app_alloc(void) {
|
||||
JsApp* app = malloc(sizeof(JsApp));
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->loading = loading_alloc();
|
||||
|
||||
app->gui = furi_record_open("gui");
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, JsAppViewLoading, loading_get_view(app->loading));
|
||||
|
||||
app->console_view = console_view_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, JsAppViewConsole, console_view_get_view(app->console_view));
|
||||
view_set_previous_callback(console_view_get_view(app->console_view), js_view_exit);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, JsAppViewConsole);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void js_app_free(JsApp* app) {
|
||||
console_view_free(app->console_view);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, JsAppViewConsole);
|
||||
loading_free(app->loading);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, JsAppViewLoading);
|
||||
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
furi_record_close("gui");
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t js_app(void* arg) {
|
||||
JsApp* app = js_app_alloc();
|
||||
|
||||
FuriString* script_path = furi_string_alloc_set(APP_ASSETS_PATH());
|
||||
do {
|
||||
if(arg != NULL && strlen(arg) > 0) {
|
||||
furi_string_set(script_path, (const char*)arg);
|
||||
} else {
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(&browser_options, ".js", &I_js_script_10px);
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
if(!dialog_file_browser_show(dialogs, script_path, script_path, &browser_options))
|
||||
break;
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
}
|
||||
FuriString* name = furi_string_alloc();
|
||||
path_extract_filename(script_path, name, false);
|
||||
FuriString* start_text =
|
||||
furi_string_alloc_printf("Running %s", furi_string_get_cstr(name));
|
||||
console_view_print(app->console_view, furi_string_get_cstr(start_text));
|
||||
console_view_print(app->console_view, "------------");
|
||||
furi_string_free(name);
|
||||
furi_string_free(start_text);
|
||||
|
||||
app->js_thread = js_thread_run(furi_string_get_cstr(script_path), js_callback, app);
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
js_thread_stop(app->js_thread);
|
||||
} while(0);
|
||||
|
||||
furi_string_free(script_path);
|
||||
|
||||
js_app_free(app);
|
||||
return 0;
|
||||
} //-V773
|
||||
10
applications/system/js_app/js_app_i.h
Normal file
10
applications/system/js_app/js_app_i.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/loading.h>
|
||||
#include "views/console_view.h"
|
||||
|
||||
typedef enum {
|
||||
JsAppViewConsole,
|
||||
JsAppViewLoading,
|
||||
} JsAppView;
|
||||
126
applications/system/js_app/js_modules.c
Normal file
126
applications/system/js_app/js_modules.c
Normal file
@@ -0,0 +1,126 @@
|
||||
#include <core/common_defines.h>
|
||||
#include "js_modules.h"
|
||||
#include <m-dict.h>
|
||||
#include "modules/js_flipper.h"
|
||||
|
||||
#define TAG "JS modules"
|
||||
|
||||
typedef struct {
|
||||
JsModeConstructor create;
|
||||
JsModeDestructor destroy;
|
||||
void* context;
|
||||
} JsModuleData;
|
||||
|
||||
DICT_DEF2(JsModuleDict, FuriString*, FURI_STRING_OPLIST, JsModuleData, M_POD_OPLIST);
|
||||
|
||||
static const JsModuleDescriptor modules_builtin[] = {
|
||||
{"flipper", js_flipper_create, NULL},
|
||||
};
|
||||
|
||||
struct JsModules {
|
||||
struct mjs* mjs;
|
||||
JsModuleDict_t module_dict;
|
||||
PluginManager* plugin_manager;
|
||||
};
|
||||
|
||||
JsModules* js_modules_create(struct mjs* mjs, CompositeApiResolver* resolver) {
|
||||
JsModules* modules = malloc(sizeof(JsModules));
|
||||
modules->mjs = mjs;
|
||||
JsModuleDict_init(modules->module_dict);
|
||||
|
||||
modules->plugin_manager = plugin_manager_alloc(
|
||||
PLUGIN_APP_ID, PLUGIN_API_VERSION, composite_api_resolver_get(resolver));
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
void js_modules_destroy(JsModules* modules) {
|
||||
JsModuleDict_it_t it;
|
||||
for(JsModuleDict_it(it, modules->module_dict); !JsModuleDict_end_p(it);
|
||||
JsModuleDict_next(it)) {
|
||||
const JsModuleDict_itref_t* module_itref = JsModuleDict_cref(it);
|
||||
if(module_itref->value.destroy) {
|
||||
module_itref->value.destroy(module_itref->value.context);
|
||||
}
|
||||
}
|
||||
plugin_manager_free(modules->plugin_manager);
|
||||
JsModuleDict_clear(modules->module_dict);
|
||||
free(modules);
|
||||
}
|
||||
|
||||
mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len) {
|
||||
FuriString* module_name = furi_string_alloc_set_str(name);
|
||||
// Check if module is already installed
|
||||
JsModuleData* module_inst = JsModuleDict_get(modules->module_dict, module_name);
|
||||
if(module_inst) { //-V547
|
||||
furi_string_free(module_name);
|
||||
mjs_prepend_errorf(
|
||||
modules->mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module is already installed", name);
|
||||
return MJS_UNDEFINED;
|
||||
}
|
||||
|
||||
bool module_found = false;
|
||||
// Check built-in modules
|
||||
for(size_t i = 0; i < COUNT_OF(modules_builtin); i++) { //-V1008
|
||||
size_t name_compare_len = strlen(modules_builtin[i].name);
|
||||
|
||||
if(name_compare_len != name_len) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(strncmp(name, modules_builtin[i].name, name_compare_len) == 0) {
|
||||
JsModuleData module = {
|
||||
.create = modules_builtin[i].create, .destroy = modules_builtin[i].destroy};
|
||||
JsModuleDict_set_at(modules->module_dict, module_name, module);
|
||||
module_found = true;
|
||||
FURI_LOG_I(TAG, "Using built-in module %s", name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// External module load
|
||||
if(!module_found) {
|
||||
FuriString* module_path = furi_string_alloc();
|
||||
furi_string_printf(module_path, "%s/js_%s.fal", APP_DATA_PATH("plugins"), name);
|
||||
FURI_LOG_I(TAG, "Loading external module %s", furi_string_get_cstr(module_path));
|
||||
do {
|
||||
uint32_t plugin_cnt_last = plugin_manager_get_count(modules->plugin_manager);
|
||||
PluginManagerError load_error = plugin_manager_load_single(
|
||||
modules->plugin_manager, furi_string_get_cstr(module_path));
|
||||
if(load_error != PluginManagerErrorNone) {
|
||||
break;
|
||||
}
|
||||
const JsModuleDescriptor* plugin =
|
||||
plugin_manager_get_ep(modules->plugin_manager, plugin_cnt_last);
|
||||
furi_assert(plugin);
|
||||
|
||||
if(strncmp(name, plugin->name, name_len) != 0) {
|
||||
FURI_LOG_E(TAG, "Module name missmatch %s", plugin->name);
|
||||
break;
|
||||
}
|
||||
JsModuleData module = {.create = plugin->create, .destroy = plugin->destroy};
|
||||
JsModuleDict_set_at(modules->module_dict, module_name, module);
|
||||
|
||||
module_found = true;
|
||||
} while(0);
|
||||
furi_string_free(module_path);
|
||||
}
|
||||
|
||||
// Run module constructor
|
||||
mjs_val_t module_object = MJS_UNDEFINED;
|
||||
if(module_found) {
|
||||
module_inst = JsModuleDict_get(modules->module_dict, module_name);
|
||||
furi_assert(module_inst);
|
||||
if(module_inst->create) { //-V779
|
||||
module_inst->context = module_inst->create(modules->mjs, &module_object);
|
||||
}
|
||||
}
|
||||
|
||||
if(module_object == MJS_UNDEFINED) { //-V547
|
||||
mjs_prepend_errorf(modules->mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module load fail", name);
|
||||
}
|
||||
|
||||
furi_string_free(module_name);
|
||||
|
||||
return module_object;
|
||||
}
|
||||
25
applications/system/js_app/js_modules.h
Normal file
25
applications/system/js_app/js_modules.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "js_thread_i.h"
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#include <flipper_application/plugins/plugin_manager.h>
|
||||
#include <flipper_application/plugins/composite_resolver.h>
|
||||
|
||||
#define PLUGIN_APP_ID "js"
|
||||
#define PLUGIN_API_VERSION 1
|
||||
|
||||
typedef void* (*JsModeConstructor)(struct mjs* mjs, mjs_val_t* object);
|
||||
typedef void (*JsModeDestructor)(void* inst);
|
||||
|
||||
typedef struct {
|
||||
char* name;
|
||||
JsModeConstructor create;
|
||||
JsModeDestructor destroy;
|
||||
} JsModuleDescriptor;
|
||||
|
||||
typedef struct JsModules JsModules;
|
||||
|
||||
JsModules* js_modules_create(struct mjs* mjs, CompositeApiResolver* resolver);
|
||||
|
||||
void js_modules_destroy(JsModules* modules);
|
||||
|
||||
mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len);
|
||||
319
applications/system/js_app/js_thread.c
Normal file
319
applications/system/js_app/js_thread.c
Normal file
@@ -0,0 +1,319 @@
|
||||
#include <common/cs_dbg.h>
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
#include <loader/firmware_api/firmware_api.h>
|
||||
#include <flipper_application/api_hashtable/api_hashtable.h>
|
||||
#include <flipper_application/plugins/composite_resolver.h>
|
||||
#include <furi_hal.h>
|
||||
#include "plugin_api/app_api_interface.h"
|
||||
#include "js_thread.h"
|
||||
#include "js_thread_i.h"
|
||||
#include "js_modules.h"
|
||||
|
||||
#define TAG "JS"
|
||||
|
||||
struct JsThread {
|
||||
FuriThread* thread;
|
||||
FuriString* path;
|
||||
CompositeApiResolver* resolver;
|
||||
JsThreadCallback app_callback;
|
||||
void* context;
|
||||
JsModules* modules;
|
||||
};
|
||||
|
||||
static void js_str_print(FuriString* msg_str, struct mjs* mjs) {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
for(size_t i = 0; i < num_args; i++) {
|
||||
char* name = NULL;
|
||||
size_t name_len = 0;
|
||||
int need_free = 0;
|
||||
mjs_val_t arg = mjs_arg(mjs, i);
|
||||
mjs_err_t err = mjs_to_string(mjs, &arg, &name, &name_len, &need_free);
|
||||
if(err != MJS_OK) {
|
||||
furi_string_cat_printf(msg_str, "err %s ", mjs_strerror(mjs, err));
|
||||
} else {
|
||||
furi_string_cat_printf(msg_str, "%s ", name);
|
||||
}
|
||||
if(need_free) {
|
||||
free(name);
|
||||
name = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void js_print(struct mjs* mjs) {
|
||||
FuriString* msg_str = furi_string_alloc();
|
||||
js_str_print(msg_str, mjs);
|
||||
|
||||
printf("%s\r\n", furi_string_get_cstr(msg_str));
|
||||
|
||||
JsThread* worker = mjs_get_context(mjs);
|
||||
furi_assert(worker);
|
||||
if(worker->app_callback) {
|
||||
worker->app_callback(JsThreadEventPrint, furi_string_get_cstr(msg_str), worker->context);
|
||||
}
|
||||
|
||||
furi_string_free(msg_str);
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_console_log(struct mjs* mjs) {
|
||||
FuriString* msg_str = furi_string_alloc();
|
||||
js_str_print(msg_str, mjs);
|
||||
FURI_LOG_I(TAG, "%s", furi_string_get_cstr(msg_str));
|
||||
furi_string_free(msg_str);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_console_warn(struct mjs* mjs) {
|
||||
FuriString* msg_str = furi_string_alloc();
|
||||
js_str_print(msg_str, mjs);
|
||||
FURI_LOG_W(TAG, "%s", furi_string_get_cstr(msg_str));
|
||||
furi_string_free(msg_str);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_console_error(struct mjs* mjs) {
|
||||
FuriString* msg_str = furi_string_alloc();
|
||||
js_str_print(msg_str, mjs);
|
||||
FURI_LOG_E(TAG, "%s", furi_string_get_cstr(msg_str));
|
||||
furi_string_free(msg_str);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_console_debug(struct mjs* mjs) {
|
||||
FuriString* msg_str = furi_string_alloc();
|
||||
js_str_print(msg_str, mjs);
|
||||
FURI_LOG_D(TAG, "%s", furi_string_get_cstr(msg_str));
|
||||
furi_string_free(msg_str);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_exit_flag_poll(struct mjs* mjs) {
|
||||
uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, 0);
|
||||
if(flags & FuriFlagError) {
|
||||
return;
|
||||
}
|
||||
if(flags & ThreadEventStop) {
|
||||
mjs_exit(mjs);
|
||||
}
|
||||
}
|
||||
|
||||
bool js_delay_with_flags(struct mjs* mjs, uint32_t time) {
|
||||
uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, time);
|
||||
if(flags & FuriFlagError) {
|
||||
return false;
|
||||
}
|
||||
if(flags & ThreadEventStop) {
|
||||
mjs_exit(mjs);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void js_flags_set(struct mjs* mjs, uint32_t flags) {
|
||||
JsThread* worker = mjs_get_context(mjs);
|
||||
furi_assert(worker);
|
||||
furi_thread_flags_set(furi_thread_get_id(worker->thread), flags);
|
||||
}
|
||||
|
||||
uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags_mask, uint32_t timeout) {
|
||||
flags_mask |= ThreadEventStop;
|
||||
uint32_t flags = furi_thread_flags_get();
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
if(flags == 0) {
|
||||
flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout);
|
||||
} else {
|
||||
uint32_t state = furi_thread_flags_clear(flags & flags_mask);
|
||||
furi_check((state & FuriFlagError) == 0);
|
||||
}
|
||||
|
||||
if(flags & FuriFlagError) {
|
||||
return 0;
|
||||
}
|
||||
if(flags & ThreadEventStop) {
|
||||
mjs_exit(mjs);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
static void js_delay(struct mjs* mjs) {
|
||||
bool args_correct = false;
|
||||
int ms = 0;
|
||||
|
||||
if(mjs_nargs(mjs) == 1) {
|
||||
mjs_val_t arg = mjs_arg(mjs, 0);
|
||||
if(mjs_is_number(arg)) {
|
||||
ms = mjs_get_int(mjs, arg);
|
||||
args_correct = true;
|
||||
}
|
||||
}
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
js_delay_with_flags(mjs, ms);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void* js_dlsym(void* handle, const char* name) {
|
||||
CompositeApiResolver* resolver = handle;
|
||||
Elf32_Addr addr = 0;
|
||||
uint32_t hash = elf_symbolname_hash(name);
|
||||
const ElfApiInterface* api = composite_api_resolver_get(resolver);
|
||||
|
||||
if(!api->resolver_callback(api, hash, &addr)) {
|
||||
FURI_LOG_E(TAG, "FFI: cannot find \"%s\"", name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (void*)addr;
|
||||
}
|
||||
|
||||
static void js_ffi_address(struct mjs* mjs) {
|
||||
mjs_val_t name_v = mjs_arg(mjs, 0);
|
||||
size_t len;
|
||||
const char* name = mjs_get_string(mjs, &name_v, &len);
|
||||
void* addr = mjs_ffi_resolve(mjs, name);
|
||||
mjs_return(mjs, mjs_mk_foreign(mjs, addr));
|
||||
}
|
||||
|
||||
static void js_require(struct mjs* mjs) {
|
||||
mjs_val_t name_v = mjs_arg(mjs, 0);
|
||||
size_t len;
|
||||
const char* name = mjs_get_string(mjs, &name_v, &len);
|
||||
mjs_val_t req_object = MJS_UNDEFINED;
|
||||
if((len == 0) || (name == NULL)) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "String argument is expected");
|
||||
} else {
|
||||
JsThread* worker = mjs_get_context(mjs);
|
||||
furi_assert(worker);
|
||||
req_object = js_module_require(worker->modules, name, len);
|
||||
}
|
||||
mjs_return(mjs, req_object);
|
||||
}
|
||||
|
||||
static void js_global_to_string(struct mjs* mjs) {
|
||||
double num = mjs_get_int(mjs, mjs_arg(mjs, 0));
|
||||
char tmp_str[] = "-2147483648";
|
||||
itoa(num, tmp_str, 10);
|
||||
mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true);
|
||||
mjs_return(mjs, ret);
|
||||
}
|
||||
|
||||
static void js_global_to_hex_string(struct mjs* mjs) {
|
||||
double num = mjs_get_int(mjs, mjs_arg(mjs, 0));
|
||||
char tmp_str[] = "-FFFFFFFF";
|
||||
itoa(num, tmp_str, 16);
|
||||
mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true);
|
||||
mjs_return(mjs, ret);
|
||||
}
|
||||
|
||||
static void js_dump_write_callback(void* ctx, const char* format, ...) {
|
||||
File* file = ctx;
|
||||
furi_assert(ctx);
|
||||
|
||||
FuriString* str = furi_string_alloc();
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
furi_string_vprintf(str, format, args);
|
||||
furi_string_cat(str, "\n");
|
||||
va_end(args);
|
||||
|
||||
storage_file_write(file, furi_string_get_cstr(str), furi_string_size(str));
|
||||
furi_string_free(str);
|
||||
}
|
||||
|
||||
static int32_t js_thread(void* arg) {
|
||||
JsThread* worker = arg;
|
||||
worker->resolver = composite_api_resolver_alloc();
|
||||
composite_api_resolver_add(worker->resolver, firmware_api_interface);
|
||||
composite_api_resolver_add(worker->resolver, application_api_interface);
|
||||
|
||||
struct mjs* mjs = mjs_create(worker);
|
||||
worker->modules = js_modules_create(mjs, worker->resolver);
|
||||
mjs_val_t global = mjs_get_global(mjs);
|
||||
mjs_set(mjs, global, "print", ~0, MJS_MK_FN(js_print));
|
||||
mjs_set(mjs, global, "delay", ~0, MJS_MK_FN(js_delay));
|
||||
mjs_set(mjs, global, "to_string", ~0, MJS_MK_FN(js_global_to_string));
|
||||
mjs_set(mjs, global, "to_hex_string", ~0, MJS_MK_FN(js_global_to_hex_string));
|
||||
mjs_set(mjs, global, "ffi_address", ~0, MJS_MK_FN(js_ffi_address));
|
||||
mjs_set(mjs, global, "require", ~0, MJS_MK_FN(js_require));
|
||||
|
||||
mjs_val_t console_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, console_obj, "log", ~0, MJS_MK_FN(js_console_log));
|
||||
mjs_set(mjs, console_obj, "warn", ~0, MJS_MK_FN(js_console_warn));
|
||||
mjs_set(mjs, console_obj, "error", ~0, MJS_MK_FN(js_console_error));
|
||||
mjs_set(mjs, console_obj, "debug", ~0, MJS_MK_FN(js_console_debug));
|
||||
mjs_set(mjs, global, "console", ~0, console_obj);
|
||||
|
||||
mjs_set_ffi_resolver(mjs, js_dlsym, worker->resolver);
|
||||
|
||||
mjs_set_exec_flags_poller(mjs, js_exit_flag_poll);
|
||||
|
||||
mjs_err_t err = mjs_exec_file(mjs, furi_string_get_cstr(worker->path), NULL);
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
FuriString* dump_path = furi_string_alloc_set(worker->path);
|
||||
furi_string_cat(dump_path, ".lst");
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* file = storage_file_alloc(storage);
|
||||
|
||||
if(storage_file_open(
|
||||
file, furi_string_get_cstr(dump_path), FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
|
||||
mjs_disasm_all(mjs, js_dump_write_callback, file);
|
||||
}
|
||||
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
furi_string_free(dump_path);
|
||||
}
|
||||
|
||||
if(err != MJS_OK) {
|
||||
FURI_LOG_E(TAG, "Exec error: %s", mjs_strerror(mjs, err));
|
||||
if(worker->app_callback) {
|
||||
worker->app_callback(JsThreadEventError, mjs_strerror(mjs, err), worker->context);
|
||||
}
|
||||
const char* stack_trace = mjs_get_stack_trace(mjs);
|
||||
if(stack_trace != NULL) {
|
||||
FURI_LOG_E(TAG, "Stack trace:\n%s", stack_trace);
|
||||
if(worker->app_callback) {
|
||||
worker->app_callback(JsThreadEventErrorTrace, stack_trace, worker->context);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(worker->app_callback) {
|
||||
worker->app_callback(JsThreadEventDone, NULL, worker->context);
|
||||
}
|
||||
}
|
||||
|
||||
js_modules_destroy(worker->modules);
|
||||
mjs_destroy(mjs);
|
||||
|
||||
composite_api_resolver_free(worker->resolver);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
JsThread* js_thread_run(const char* script_path, JsThreadCallback callback, void* context) {
|
||||
JsThread* worker = malloc(sizeof(JsThread)); //-V799
|
||||
worker->path = furi_string_alloc_set(script_path);
|
||||
worker->thread = furi_thread_alloc_ex("JsThread", 8 * 1024, js_thread, worker);
|
||||
worker->app_callback = callback;
|
||||
worker->context = context;
|
||||
furi_thread_start(worker->thread);
|
||||
return worker;
|
||||
}
|
||||
|
||||
void js_thread_stop(JsThread* worker) {
|
||||
furi_thread_flags_set(furi_thread_get_id(worker->thread), ThreadEventStop);
|
||||
furi_thread_join(worker->thread);
|
||||
furi_thread_free(worker->thread);
|
||||
furi_string_free(worker->path);
|
||||
free(worker);
|
||||
}
|
||||
16
applications/system/js_app/js_thread.h
Normal file
16
applications/system/js_app/js_thread.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct JsThread JsThread;
|
||||
|
||||
typedef enum {
|
||||
JsThreadEventDone,
|
||||
JsThreadEventError,
|
||||
JsThreadEventPrint,
|
||||
JsThreadEventErrorTrace,
|
||||
} JsThreadEvent;
|
||||
|
||||
typedef void (*JsThreadCallback)(JsThreadEvent event, const char* msg, void* context);
|
||||
|
||||
JsThread* js_thread_run(const char* script_path, JsThreadCallback callback, void* context);
|
||||
|
||||
void js_thread_stop(JsThread* worker);
|
||||
25
applications/system/js_app/js_thread_i.h
Normal file
25
applications/system/js_app/js_thread_i.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <mjs_core_public.h>
|
||||
#include <mjs_ffi_public.h>
|
||||
#include <mjs_exec_public.h>
|
||||
#include <mjs_object_public.h>
|
||||
#include <mjs_string_public.h>
|
||||
#include <mjs_array_public.h>
|
||||
#include <mjs_util_public.h>
|
||||
#include <mjs_primitive_public.h>
|
||||
#include <mjs_array_buf_public.h>
|
||||
|
||||
#define INST_PROP_NAME "_"
|
||||
|
||||
typedef enum {
|
||||
ThreadEventStop = (1 << 0),
|
||||
ThreadEventCustomDataRx = (1 << 1),
|
||||
} WorkerEventFlags;
|
||||
|
||||
bool js_delay_with_flags(struct mjs* mjs, uint32_t time);
|
||||
|
||||
void js_flags_set(struct mjs* mjs, uint32_t flags);
|
||||
|
||||
uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags, uint32_t timeout);
|
||||
410
applications/system/js_app/modules/js_badusb.c
Normal file
410
applications/system/js_app/modules/js_badusb.c
Normal file
@@ -0,0 +1,410 @@
|
||||
#include <core/common_defines.h>
|
||||
#include "../js_modules.h"
|
||||
#include <furi_hal.h>
|
||||
|
||||
typedef struct {
|
||||
FuriHalUsbHidConfig* hid_cfg;
|
||||
FuriHalUsbInterface* usb_if_prev;
|
||||
uint8_t key_hold_cnt;
|
||||
} JsBadusbInst;
|
||||
|
||||
static const struct {
|
||||
char* name;
|
||||
uint16_t code;
|
||||
} key_codes[] = {
|
||||
{"CTRL", KEY_MOD_LEFT_CTRL},
|
||||
{"SHIFT", KEY_MOD_LEFT_SHIFT},
|
||||
{"ALT", KEY_MOD_LEFT_ALT},
|
||||
{"GUI", KEY_MOD_LEFT_GUI},
|
||||
|
||||
{"DOWN", HID_KEYBOARD_DOWN_ARROW},
|
||||
{"LEFT", HID_KEYBOARD_LEFT_ARROW},
|
||||
{"RIGHT", HID_KEYBOARD_RIGHT_ARROW},
|
||||
{"UP", HID_KEYBOARD_UP_ARROW},
|
||||
|
||||
{"ENTER", HID_KEYBOARD_RETURN},
|
||||
{"PAUSE", HID_KEYBOARD_PAUSE},
|
||||
{"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK},
|
||||
{"DELETE", HID_KEYBOARD_DELETE_FORWARD},
|
||||
{"BACKSPACE", HID_KEYBOARD_DELETE},
|
||||
{"END", HID_KEYBOARD_END},
|
||||
{"ESC", HID_KEYBOARD_ESCAPE},
|
||||
{"HOME", HID_KEYBOARD_HOME},
|
||||
{"INSERT", HID_KEYBOARD_INSERT},
|
||||
{"NUMLOCK", HID_KEYPAD_NUMLOCK},
|
||||
{"PAGEUP", HID_KEYBOARD_PAGE_UP},
|
||||
{"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN},
|
||||
{"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN},
|
||||
{"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK},
|
||||
{"SPACE", HID_KEYBOARD_SPACEBAR},
|
||||
{"TAB", HID_KEYBOARD_TAB},
|
||||
{"MENU", HID_KEYBOARD_APPLICATION},
|
||||
|
||||
{"F1", HID_KEYBOARD_F1},
|
||||
{"F2", HID_KEYBOARD_F2},
|
||||
{"F3", HID_KEYBOARD_F3},
|
||||
{"F4", HID_KEYBOARD_F4},
|
||||
{"F5", HID_KEYBOARD_F5},
|
||||
{"F6", HID_KEYBOARD_F6},
|
||||
{"F7", HID_KEYBOARD_F7},
|
||||
{"F8", HID_KEYBOARD_F8},
|
||||
{"F9", HID_KEYBOARD_F9},
|
||||
{"F10", HID_KEYBOARD_F10},
|
||||
{"F11", HID_KEYBOARD_F11},
|
||||
{"F12", HID_KEYBOARD_F12},
|
||||
};
|
||||
|
||||
static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConfig* hid_cfg) {
|
||||
if(!mjs_is_object(arg)) {
|
||||
return false;
|
||||
}
|
||||
mjs_val_t vid_obj = mjs_get(mjs, arg, "vid", ~0);
|
||||
mjs_val_t pid_obj = mjs_get(mjs, arg, "pid", ~0);
|
||||
mjs_val_t mfr_obj = mjs_get(mjs, arg, "mfr_name", ~0);
|
||||
mjs_val_t prod_obj = mjs_get(mjs, arg, "prod_name", ~0);
|
||||
|
||||
if(mjs_is_number(vid_obj) && mjs_is_number(pid_obj)) {
|
||||
hid_cfg->vid = mjs_get_int32(mjs, vid_obj);
|
||||
hid_cfg->pid = mjs_get_int32(mjs, pid_obj);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(mjs_is_string(mfr_obj)) {
|
||||
size_t str_len = 0;
|
||||
const char* str_temp = mjs_get_string(mjs, &mfr_obj, &str_len);
|
||||
if((str_len == 0) || (str_temp == NULL)) {
|
||||
return false;
|
||||
}
|
||||
strlcpy(hid_cfg->manuf, str_temp, sizeof(hid_cfg->manuf));
|
||||
}
|
||||
|
||||
if(mjs_is_string(prod_obj)) {
|
||||
size_t str_len = 0;
|
||||
const char* str_temp = mjs_get_string(mjs, &prod_obj, &str_len);
|
||||
if((str_len == 0) || (str_temp == NULL)) {
|
||||
return false;
|
||||
}
|
||||
strlcpy(hid_cfg->product, str_temp, sizeof(hid_cfg->product));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void js_badusb_setup(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(badusb);
|
||||
|
||||
if(badusb->usb_if_prev) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is already started");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
bool args_correct = false;
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args == 0) {
|
||||
// No arguments: start USB HID with default settings
|
||||
args_correct = true;
|
||||
} else if(num_args == 1) {
|
||||
badusb->hid_cfg = malloc(sizeof(FuriHalUsbHidConfig));
|
||||
// Parse argument object
|
||||
args_correct = setup_parse_params(mjs, mjs_arg(mjs, 0), badusb->hid_cfg);
|
||||
}
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
badusb->usb_if_prev = furi_hal_usb_get_config();
|
||||
|
||||
if(!furi_hal_usb_set_config(&usb_hid, badusb->hid_cfg)) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "USB is locked, close companion app first");
|
||||
badusb->usb_if_prev = NULL;
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_badusb_is_connected(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(badusb);
|
||||
|
||||
if(badusb->usb_if_prev == NULL) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_connected = furi_hal_hid_is_connected();
|
||||
mjs_return(mjs, mjs_mk_boolean(mjs, is_connected));
|
||||
}
|
||||
|
||||
uint16_t get_keycode_by_name(const char* key_name, size_t name_len) {
|
||||
if(name_len == 1) { // Single char
|
||||
return (HID_ASCII_TO_KEY(key_name[0]));
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(key_codes); i++) {
|
||||
size_t key_cmd_len = strlen(key_codes[i].name);
|
||||
if(key_cmd_len != name_len) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(strncmp(key_name, key_codes[i].name, name_len) == 0) {
|
||||
return key_codes[i].code;
|
||||
}
|
||||
}
|
||||
|
||||
return HID_KEYBOARD_NONE;
|
||||
}
|
||||
|
||||
static bool parse_keycode(struct mjs* mjs, size_t nargs, uint16_t* keycode) {
|
||||
uint16_t key_tmp = 0;
|
||||
for(size_t i = 0; i < nargs; i++) {
|
||||
mjs_val_t arg = mjs_arg(mjs, i);
|
||||
if(mjs_is_string(arg)) {
|
||||
size_t name_len = 0;
|
||||
const char* key_name = mjs_get_string(mjs, &arg, &name_len);
|
||||
if((key_name == NULL) || (name_len == 0)) {
|
||||
// String error
|
||||
return false;
|
||||
}
|
||||
uint16_t str_key = get_keycode_by_name(key_name, name_len);
|
||||
if(str_key == HID_KEYBOARD_NONE) {
|
||||
// Unknown key code
|
||||
return false;
|
||||
}
|
||||
if((str_key & 0xFF) && (key_tmp & 0xFF)) {
|
||||
// Main key is already defined
|
||||
return false;
|
||||
}
|
||||
key_tmp |= str_key;
|
||||
} else if(mjs_is_number(arg)) {
|
||||
uint32_t keycode_number = (uint32_t)mjs_get_int32(mjs, arg);
|
||||
if(((key_tmp & 0xFF) != 0) || (keycode_number > 0xFF)) {
|
||||
return false;
|
||||
}
|
||||
key_tmp |= keycode_number & 0xFF;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*keycode = key_tmp;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void js_badusb_press(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(badusb);
|
||||
if(badusb->usb_if_prev == NULL) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
bool args_correct = false;
|
||||
uint16_t keycode = HID_KEYBOARD_NONE;
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args > 0) {
|
||||
args_correct = parse_keycode(mjs, num_args, &keycode);
|
||||
}
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
furi_hal_hid_kb_press(keycode);
|
||||
furi_hal_hid_kb_release(keycode);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_badusb_hold(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(badusb);
|
||||
if(badusb->usb_if_prev == NULL) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
bool args_correct = false;
|
||||
uint16_t keycode = HID_KEYBOARD_NONE;
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args > 0) {
|
||||
args_correct = parse_keycode(mjs, num_args, &keycode);
|
||||
}
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
if(keycode & 0xFF) {
|
||||
badusb->key_hold_cnt++;
|
||||
if(badusb->key_hold_cnt > (HID_KB_MAX_KEYS - 1)) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Too many keys are hold");
|
||||
furi_hal_hid_kb_release_all();
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
furi_hal_hid_kb_press(keycode);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_badusb_release(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(badusb);
|
||||
if(badusb->usb_if_prev == NULL) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
bool args_correct = false;
|
||||
uint16_t keycode = HID_KEYBOARD_NONE;
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args == 0) {
|
||||
furi_hal_hid_kb_release_all();
|
||||
badusb->key_hold_cnt = 0;
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
} else {
|
||||
args_correct = parse_keycode(mjs, num_args, &keycode);
|
||||
}
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
if((keycode & 0xFF) && (badusb->key_hold_cnt > 0)) {
|
||||
badusb->key_hold_cnt--;
|
||||
}
|
||||
furi_hal_hid_kb_release(keycode);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void badusb_print(struct mjs* mjs, bool ln) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(badusb);
|
||||
if(badusb->usb_if_prev == NULL) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
bool args_correct = false;
|
||||
const char* text_str = NULL;
|
||||
size_t text_len = 0;
|
||||
uint32_t delay_val = 0;
|
||||
do {
|
||||
mjs_val_t obj_string = MJS_UNDEFINED;
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args == 1) {
|
||||
obj_string = mjs_arg(mjs, 0);
|
||||
} else if(num_args == 2) {
|
||||
obj_string = mjs_arg(mjs, 0);
|
||||
mjs_val_t obj_delay = mjs_arg(mjs, 1);
|
||||
if(!mjs_is_number(obj_delay)) {
|
||||
break;
|
||||
}
|
||||
delay_val = (uint32_t)mjs_get_int32(mjs, obj_delay);
|
||||
if(delay_val > 60000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!mjs_is_string(obj_string)) {
|
||||
break;
|
||||
}
|
||||
text_str = mjs_get_string(mjs, &obj_string, &text_len);
|
||||
if((text_str == NULL) || (text_len == 0)) {
|
||||
break;
|
||||
}
|
||||
args_correct = true;
|
||||
} while(0);
|
||||
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < text_len; i++) {
|
||||
uint16_t keycode = HID_ASCII_TO_KEY(text_str[i]);
|
||||
furi_hal_hid_kb_press(keycode);
|
||||
furi_hal_hid_kb_release(keycode);
|
||||
if(delay_val > 0) {
|
||||
bool need_exit = js_delay_with_flags(mjs, delay_val);
|
||||
if(need_exit) {
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(ln) {
|
||||
furi_hal_hid_kb_press(HID_KEYBOARD_RETURN);
|
||||
furi_hal_hid_kb_release(HID_KEYBOARD_RETURN);
|
||||
}
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_badusb_print(struct mjs* mjs) {
|
||||
badusb_print(mjs, false);
|
||||
}
|
||||
|
||||
static void js_badusb_println(struct mjs* mjs) {
|
||||
badusb_print(mjs, true);
|
||||
}
|
||||
|
||||
static void* js_badusb_create(struct mjs* mjs, mjs_val_t* object) {
|
||||
JsBadusbInst* badusb = malloc(sizeof(JsBadusbInst));
|
||||
mjs_val_t badusb_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, badusb_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, badusb));
|
||||
mjs_set(mjs, badusb_obj, "setup", ~0, MJS_MK_FN(js_badusb_setup));
|
||||
mjs_set(mjs, badusb_obj, "isConnected", ~0, MJS_MK_FN(js_badusb_is_connected));
|
||||
mjs_set(mjs, badusb_obj, "press", ~0, MJS_MK_FN(js_badusb_press));
|
||||
mjs_set(mjs, badusb_obj, "hold", ~0, MJS_MK_FN(js_badusb_hold));
|
||||
mjs_set(mjs, badusb_obj, "release", ~0, MJS_MK_FN(js_badusb_release));
|
||||
mjs_set(mjs, badusb_obj, "print", ~0, MJS_MK_FN(js_badusb_print));
|
||||
mjs_set(mjs, badusb_obj, "println", ~0, MJS_MK_FN(js_badusb_println));
|
||||
*object = badusb_obj;
|
||||
return badusb;
|
||||
}
|
||||
|
||||
static void js_badusb_destroy(void* inst) {
|
||||
JsBadusbInst* badusb = inst;
|
||||
if(badusb->usb_if_prev) {
|
||||
furi_hal_hid_kb_release_all();
|
||||
furi_check(furi_hal_usb_set_config(badusb->usb_if_prev, NULL));
|
||||
}
|
||||
if(badusb->hid_cfg) {
|
||||
free(badusb->hid_cfg);
|
||||
}
|
||||
free(badusb);
|
||||
}
|
||||
|
||||
static const JsModuleDescriptor js_badusb_desc = {
|
||||
"badusb",
|
||||
js_badusb_create,
|
||||
js_badusb_destroy,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor plugin_descriptor = {
|
||||
.appid = PLUGIN_APP_ID,
|
||||
.ep_api_version = PLUGIN_API_VERSION,
|
||||
.entry_point = &js_badusb_desc,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* js_badusb_ep(void) {
|
||||
return &plugin_descriptor;
|
||||
}
|
||||
154
applications/system/js_app/modules/js_dialog.c
Normal file
154
applications/system/js_app/modules/js_dialog.c
Normal file
@@ -0,0 +1,154 @@
|
||||
#include <core/common_defines.h>
|
||||
#include "../js_modules.h"
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
static bool js_dialog_msg_parse_params(struct mjs* mjs, const char** hdr, const char** msg) {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args != 2) {
|
||||
return false;
|
||||
}
|
||||
mjs_val_t header_obj = mjs_arg(mjs, 0);
|
||||
mjs_val_t msg_obj = mjs_arg(mjs, 1);
|
||||
if((!mjs_is_string(header_obj)) || (!mjs_is_string(msg_obj))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t arg_len = 0;
|
||||
*hdr = mjs_get_string(mjs, &header_obj, &arg_len);
|
||||
if(arg_len == 0) {
|
||||
*hdr = NULL;
|
||||
}
|
||||
|
||||
*msg = mjs_get_string(mjs, &msg_obj, &arg_len);
|
||||
if(arg_len == 0) {
|
||||
*msg = NULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void js_dialog_message(struct mjs* mjs) {
|
||||
const char* dialog_header = NULL;
|
||||
const char* dialog_msg = NULL;
|
||||
if(!js_dialog_msg_parse_params(mjs, &dialog_header, &dialog_msg)) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
dialog_message_set_buttons(message, NULL, "OK", NULL);
|
||||
if(dialog_header) {
|
||||
dialog_message_set_header(message, dialog_header, 64, 3, AlignCenter, AlignTop);
|
||||
}
|
||||
if(dialog_msg) {
|
||||
dialog_message_set_text(message, dialog_msg, 64, 26, AlignCenter, AlignTop);
|
||||
}
|
||||
DialogMessageButton result = dialog_message_show(dialogs, message);
|
||||
dialog_message_free(message);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
mjs_return(mjs, mjs_mk_boolean(mjs, result == DialogMessageButtonCenter));
|
||||
}
|
||||
|
||||
static void js_dialog_custom(struct mjs* mjs) {
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
|
||||
bool params_correct = false;
|
||||
|
||||
do {
|
||||
if(mjs_nargs(mjs) != 1) {
|
||||
break;
|
||||
}
|
||||
mjs_val_t params_obj = mjs_arg(mjs, 0);
|
||||
if(!mjs_is_object(params_obj)) {
|
||||
break;
|
||||
}
|
||||
|
||||
mjs_val_t text_obj = mjs_get(mjs, params_obj, "header", ~0);
|
||||
size_t arg_len = 0;
|
||||
const char* text_str = mjs_get_string(mjs, &text_obj, &arg_len);
|
||||
if(arg_len == 0) {
|
||||
text_str = NULL;
|
||||
}
|
||||
if(text_str) {
|
||||
dialog_message_set_header(message, text_str, 64, 3, AlignCenter, AlignTop);
|
||||
}
|
||||
|
||||
text_obj = mjs_get(mjs, params_obj, "text", ~0);
|
||||
text_str = mjs_get_string(mjs, &text_obj, &arg_len);
|
||||
if(arg_len == 0) {
|
||||
text_str = NULL;
|
||||
}
|
||||
if(text_str) {
|
||||
dialog_message_set_text(message, text_str, 64, 26, AlignCenter, AlignTop);
|
||||
}
|
||||
|
||||
mjs_val_t btn_obj[3] = {
|
||||
mjs_get(mjs, params_obj, "button_left", ~0),
|
||||
mjs_get(mjs, params_obj, "button_center", ~0),
|
||||
mjs_get(mjs, params_obj, "button_right", ~0),
|
||||
};
|
||||
const char* btn_text[3] = {NULL, NULL, NULL};
|
||||
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
if(!mjs_is_string(btn_obj[i])) {
|
||||
continue;
|
||||
}
|
||||
btn_text[i] = mjs_get_string(mjs, &btn_obj[i], &arg_len);
|
||||
if(arg_len == 0) {
|
||||
btn_text[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
dialog_message_set_buttons(message, btn_text[0], btn_text[1], btn_text[2]);
|
||||
|
||||
DialogMessageButton result = dialog_message_show(dialogs, message);
|
||||
mjs_val_t return_obj = MJS_UNDEFINED;
|
||||
if(result == DialogMessageButtonLeft) {
|
||||
return_obj = mjs_mk_string(mjs, btn_text[0], ~0, true);
|
||||
} else if(result == DialogMessageButtonCenter) {
|
||||
return_obj = mjs_mk_string(mjs, btn_text[1], ~0, true);
|
||||
} else if(result == DialogMessageButtonRight) {
|
||||
return_obj = mjs_mk_string(mjs, btn_text[2], ~0, true);
|
||||
} else {
|
||||
return_obj = mjs_mk_string(mjs, "", ~0, true);
|
||||
}
|
||||
|
||||
mjs_return(mjs, return_obj);
|
||||
params_correct = true;
|
||||
} while(0);
|
||||
|
||||
dialog_message_free(message);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
|
||||
if(!params_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
}
|
||||
|
||||
static void* js_dialog_create(struct mjs* mjs, mjs_val_t* object) {
|
||||
mjs_val_t dialog_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, dialog_obj, "message", ~0, MJS_MK_FN(js_dialog_message));
|
||||
mjs_set(mjs, dialog_obj, "custom", ~0, MJS_MK_FN(js_dialog_custom));
|
||||
*object = dialog_obj;
|
||||
|
||||
return (void*)1;
|
||||
}
|
||||
|
||||
static const JsModuleDescriptor js_dialog_desc = {
|
||||
"dialog",
|
||||
js_dialog_create,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor plugin_descriptor = {
|
||||
.appid = PLUGIN_APP_ID,
|
||||
.ep_api_version = PLUGIN_API_VERSION,
|
||||
.entry_point = &js_dialog_desc,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* js_dialog_ep(void) {
|
||||
return &plugin_descriptor;
|
||||
}
|
||||
36
applications/system/js_app/modules/js_flipper.c
Normal file
36
applications/system/js_app/modules/js_flipper.c
Normal file
@@ -0,0 +1,36 @@
|
||||
#include <core/common_defines.h>
|
||||
#include "../js_modules.h"
|
||||
#include <furi_hal_version.h>
|
||||
#include <power/power_service/power.h>
|
||||
|
||||
static void js_flipper_get_model(struct mjs* mjs) {
|
||||
mjs_val_t ret = mjs_mk_string(mjs, furi_hal_version_get_model_name(), ~0, true);
|
||||
mjs_return(mjs, ret);
|
||||
}
|
||||
|
||||
static void js_flipper_get_name(struct mjs* mjs) {
|
||||
const char* name_str = furi_hal_version_get_name_ptr();
|
||||
if(name_str == NULL) {
|
||||
name_str = "Unknown";
|
||||
}
|
||||
mjs_val_t ret = mjs_mk_string(mjs, name_str, ~0, true);
|
||||
mjs_return(mjs, ret);
|
||||
}
|
||||
|
||||
static void js_flipper_get_battery(struct mjs* mjs) {
|
||||
Power* power = furi_record_open(RECORD_POWER);
|
||||
PowerInfo info;
|
||||
power_get_info(power, &info);
|
||||
furi_record_close(RECORD_POWER);
|
||||
mjs_return(mjs, mjs_mk_number(mjs, info.charge));
|
||||
}
|
||||
|
||||
void* js_flipper_create(struct mjs* mjs, mjs_val_t* object) {
|
||||
mjs_val_t flipper_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, flipper_obj, "getModel", ~0, MJS_MK_FN(js_flipper_get_model));
|
||||
mjs_set(mjs, flipper_obj, "getName", ~0, MJS_MK_FN(js_flipper_get_name));
|
||||
mjs_set(mjs, flipper_obj, "getBatteryCharge", ~0, MJS_MK_FN(js_flipper_get_battery));
|
||||
*object = flipper_obj;
|
||||
|
||||
return (void*)1;
|
||||
}
|
||||
4
applications/system/js_app/modules/js_flipper.h
Normal file
4
applications/system/js_app/modules/js_flipper.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include "../js_thread_i.h"
|
||||
|
||||
void* js_flipper_create(struct mjs* mjs, mjs_val_t* object);
|
||||
109
applications/system/js_app/modules/js_notification.c
Normal file
109
applications/system/js_app/modules/js_notification.c
Normal file
@@ -0,0 +1,109 @@
|
||||
#include <core/common_defines.h>
|
||||
#include "../js_modules.h"
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
static void js_notify(struct mjs* mjs, const NotificationSequence* sequence) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
NotificationApp* notification = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(notification);
|
||||
notification_message(notification, sequence);
|
||||
}
|
||||
|
||||
static void js_notify_success(struct mjs* mjs) {
|
||||
js_notify(mjs, &sequence_success);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_notify_error(struct mjs* mjs) {
|
||||
js_notify(mjs, &sequence_error);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static const struct {
|
||||
const char* color_name;
|
||||
const NotificationSequence* sequence_short;
|
||||
const NotificationSequence* sequence_long;
|
||||
} led_sequences[] = {
|
||||
{"blue", &sequence_blink_blue_10, &sequence_blink_blue_100},
|
||||
{"red", &sequence_blink_red_10, &sequence_blink_red_100},
|
||||
{"green", &sequence_blink_green_10, &sequence_blink_green_100},
|
||||
{"yellow", &sequence_blink_yellow_10, &sequence_blink_yellow_100},
|
||||
{"cyan", &sequence_blink_cyan_10, &sequence_blink_cyan_100},
|
||||
{"magenta", &sequence_blink_magenta_10, &sequence_blink_magenta_100},
|
||||
};
|
||||
|
||||
static void js_notify_blink(struct mjs* mjs) {
|
||||
const NotificationSequence* sequence = NULL;
|
||||
do {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args != 2) {
|
||||
break;
|
||||
}
|
||||
mjs_val_t color_obj = mjs_arg(mjs, 0);
|
||||
mjs_val_t type_obj = mjs_arg(mjs, 1);
|
||||
if((!mjs_is_string(color_obj)) || (!mjs_is_string(type_obj))) break;
|
||||
|
||||
size_t arg_len = 0;
|
||||
const char* arg_str = mjs_get_string(mjs, &color_obj, &arg_len);
|
||||
if((arg_len == 0) || (arg_str == NULL)) break;
|
||||
|
||||
int32_t color_id = -1;
|
||||
for(size_t i = 0; i < COUNT_OF(led_sequences); i++) {
|
||||
size_t name_len = strlen(led_sequences[i].color_name);
|
||||
if(arg_len != name_len) continue;
|
||||
if(strncmp(arg_str, led_sequences[i].color_name, arg_len) == 0) {
|
||||
color_id = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(color_id == -1) break;
|
||||
|
||||
arg_str = mjs_get_string(mjs, &type_obj, &arg_len);
|
||||
if((arg_len == 0) || (arg_str == NULL)) break;
|
||||
if(strncmp(arg_str, "short", arg_len) == 0) {
|
||||
sequence = led_sequences[color_id].sequence_short;
|
||||
} else if(strncmp(arg_str, "long", arg_len) == 0) {
|
||||
sequence = led_sequences[color_id].sequence_long;
|
||||
}
|
||||
} while(0);
|
||||
|
||||
if(sequence == NULL) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
} else {
|
||||
js_notify(mjs, sequence);
|
||||
}
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void* js_notification_create(struct mjs* mjs, mjs_val_t* object) {
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
mjs_val_t notify_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, notify_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, notification));
|
||||
mjs_set(mjs, notify_obj, "success", ~0, MJS_MK_FN(js_notify_success));
|
||||
mjs_set(mjs, notify_obj, "error", ~0, MJS_MK_FN(js_notify_error));
|
||||
mjs_set(mjs, notify_obj, "blink", ~0, MJS_MK_FN(js_notify_blink));
|
||||
*object = notify_obj;
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
static void js_notification_destroy(void* inst) {
|
||||
UNUSED(inst);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
}
|
||||
|
||||
static const JsModuleDescriptor js_notification_desc = {
|
||||
"notification",
|
||||
js_notification_create,
|
||||
js_notification_destroy,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor plugin_descriptor = {
|
||||
.appid = PLUGIN_APP_ID,
|
||||
.ep_api_version = PLUGIN_API_VERSION,
|
||||
.entry_point = &js_notification_desc,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* js_notification_ep(void) {
|
||||
return &plugin_descriptor;
|
||||
}
|
||||
585
applications/system/js_app/modules/js_uart.c
Normal file
585
applications/system/js_app/modules/js_uart.c
Normal file
@@ -0,0 +1,585 @@
|
||||
#include <core/common_defines.h>
|
||||
#include <furi_hal.h>
|
||||
#include "../js_modules.h"
|
||||
#include <m-array.h>
|
||||
|
||||
#define TAG "js_uart"
|
||||
#define RX_BUF_LEN 2048
|
||||
|
||||
typedef struct {
|
||||
bool setup_done;
|
||||
FuriStreamBuffer* rx_stream;
|
||||
FuriHalSerialHandle* serial_handle;
|
||||
struct mjs* mjs;
|
||||
} JsUartInst;
|
||||
|
||||
typedef struct {
|
||||
size_t len;
|
||||
char* data;
|
||||
} PatternArrayItem;
|
||||
|
||||
ARRAY_DEF(PatternArray, PatternArrayItem, M_POD_OPLIST);
|
||||
|
||||
static void
|
||||
js_uart_on_async_rx(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) {
|
||||
JsUartInst* uart = context;
|
||||
furi_assert(uart);
|
||||
|
||||
if(event & FuriHalSerialRxEventData) {
|
||||
uint8_t data = furi_hal_serial_async_rx(handle);
|
||||
furi_stream_buffer_send(uart->rx_stream, &data, 1, 0);
|
||||
js_flags_set(uart->mjs, ThreadEventCustomDataRx);
|
||||
}
|
||||
}
|
||||
|
||||
static void js_uart_setup(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsUartInst* uart = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(uart);
|
||||
|
||||
if(uart->setup_done) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is already configured");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
bool args_correct = false;
|
||||
uint32_t baudrate = 0;
|
||||
|
||||
if(mjs_nargs(mjs) == 1) {
|
||||
mjs_val_t arg = mjs_arg(mjs, 0);
|
||||
if(mjs_is_number(arg)) {
|
||||
baudrate = mjs_get_int32(mjs, arg);
|
||||
args_correct = true;
|
||||
}
|
||||
}
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
uart->rx_stream = furi_stream_buffer_alloc(RX_BUF_LEN, 1);
|
||||
uart->serial_handle = furi_hal_serial_control_acquire(FuriHalSerialIdLpuart);
|
||||
if(uart->serial_handle) {
|
||||
furi_hal_serial_init(uart->serial_handle, baudrate);
|
||||
furi_hal_serial_async_rx_start(uart->serial_handle, js_uart_on_async_rx, uart, false);
|
||||
uart->setup_done = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void js_uart_write(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsUartInst* uart = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(uart);
|
||||
if(!uart->setup_done) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
bool args_correct = true;
|
||||
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
for(size_t i = 0; i < num_args; i++) {
|
||||
mjs_val_t arg = mjs_arg(mjs, i);
|
||||
if(mjs_is_string(arg)) {
|
||||
size_t str_len = 0;
|
||||
const char* arg_str = mjs_get_string(mjs, &arg, &str_len);
|
||||
if((str_len == 0) || (arg_str == NULL)) {
|
||||
args_correct = false;
|
||||
break;
|
||||
}
|
||||
furi_hal_serial_tx(uart->serial_handle, (uint8_t*)arg_str, str_len);
|
||||
} else if(mjs_is_number(arg)) {
|
||||
uint32_t byte_val = mjs_get_int32(mjs, arg);
|
||||
if(byte_val > 0xFF) {
|
||||
args_correct = false;
|
||||
break;
|
||||
}
|
||||
furi_hal_serial_tx(uart->serial_handle, (uint8_t*)&byte_val, 1);
|
||||
} else if(mjs_is_array(arg)) {
|
||||
size_t array_len = mjs_array_length(mjs, arg);
|
||||
for(size_t i = 0; i < array_len; i++) {
|
||||
mjs_val_t array_arg = mjs_array_get(mjs, arg, i);
|
||||
if(!mjs_is_number(array_arg)) {
|
||||
args_correct = false;
|
||||
break;
|
||||
}
|
||||
uint32_t byte_val = mjs_get_int32(mjs, array_arg);
|
||||
if(byte_val > 0xFF) {
|
||||
args_correct = false;
|
||||
break;
|
||||
}
|
||||
furi_hal_serial_tx(uart->serial_handle, (uint8_t*)&byte_val, 1);
|
||||
}
|
||||
if(!args_correct) {
|
||||
break;
|
||||
}
|
||||
} else if(mjs_is_typed_array(arg)) {
|
||||
mjs_val_t array_buf = arg;
|
||||
if(mjs_is_data_view(arg)) {
|
||||
array_buf = mjs_dataview_get_buf(mjs, arg);
|
||||
}
|
||||
size_t len = 0;
|
||||
char* buf = mjs_array_buf_get_ptr(mjs, array_buf, &len);
|
||||
furi_hal_serial_tx(uart->serial_handle, (uint8_t*)buf, len);
|
||||
} else {
|
||||
args_correct = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
}
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static size_t js_uart_receive(JsUartInst* uart, char* buf, size_t len, uint32_t timeout) {
|
||||
size_t bytes_read = 0;
|
||||
while(1) {
|
||||
uint32_t flags = ThreadEventCustomDataRx;
|
||||
if(furi_stream_buffer_is_empty(uart->rx_stream)) {
|
||||
flags = js_flags_wait(uart->mjs, ThreadEventCustomDataRx, timeout);
|
||||
}
|
||||
if(flags == 0) { // Timeout
|
||||
break;
|
||||
} else if(flags & ThreadEventStop) { // Exit flag
|
||||
bytes_read = 0;
|
||||
break;
|
||||
} else if(flags & ThreadEventCustomDataRx) { // New data received
|
||||
size_t rx_len =
|
||||
furi_stream_buffer_receive(uart->rx_stream, &buf[bytes_read], len - bytes_read, 0);
|
||||
bytes_read += rx_len;
|
||||
if(bytes_read == len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
static void js_uart_read(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsUartInst* uart = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(uart);
|
||||
if(!uart->setup_done) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t read_len = 0;
|
||||
uint32_t timeout = FuriWaitForever;
|
||||
|
||||
do {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args == 1) {
|
||||
mjs_val_t arg = mjs_arg(mjs, 0);
|
||||
if(!mjs_is_number(arg)) {
|
||||
break;
|
||||
}
|
||||
read_len = mjs_get_int32(mjs, arg);
|
||||
} else if(num_args == 2) {
|
||||
mjs_val_t len_arg = mjs_arg(mjs, 0);
|
||||
mjs_val_t timeout_arg = mjs_arg(mjs, 1);
|
||||
if((!mjs_is_number(len_arg)) || (!mjs_is_number(timeout_arg))) {
|
||||
break;
|
||||
}
|
||||
read_len = mjs_get_int32(mjs, len_arg);
|
||||
timeout = mjs_get_int32(mjs, timeout_arg);
|
||||
}
|
||||
} while(0);
|
||||
|
||||
if(read_len == 0) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
char* read_buf = malloc(read_len);
|
||||
size_t bytes_read = js_uart_receive(uart, read_buf, read_len, timeout);
|
||||
|
||||
mjs_val_t return_obj = MJS_UNDEFINED;
|
||||
if(bytes_read > 0) {
|
||||
return_obj = mjs_mk_string(mjs, read_buf, bytes_read, true);
|
||||
}
|
||||
mjs_return(mjs, return_obj);
|
||||
free(read_buf);
|
||||
}
|
||||
|
||||
static void js_uart_readln(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsUartInst* uart = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(uart);
|
||||
if(!uart->setup_done) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
bool args_correct = false;
|
||||
uint32_t timeout = FuriWaitForever;
|
||||
|
||||
do {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args > 1) {
|
||||
break;
|
||||
} else if(num_args == 1) {
|
||||
mjs_val_t arg = mjs_arg(mjs, 0);
|
||||
if(!mjs_is_number(arg)) {
|
||||
break;
|
||||
}
|
||||
timeout = mjs_get_int32(mjs, arg);
|
||||
}
|
||||
args_correct = true;
|
||||
} while(0);
|
||||
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
FuriString* rx_buf = furi_string_alloc();
|
||||
size_t bytes_read = 0;
|
||||
char read_char = 0;
|
||||
|
||||
while(1) {
|
||||
size_t read_len = js_uart_receive(uart, &read_char, 1, timeout);
|
||||
if(read_len != 1) {
|
||||
break;
|
||||
}
|
||||
if((read_char == '\r') || (read_char == '\n')) {
|
||||
break;
|
||||
} else {
|
||||
furi_string_push_back(rx_buf, read_char);
|
||||
bytes_read++;
|
||||
}
|
||||
}
|
||||
|
||||
mjs_val_t return_obj = MJS_UNDEFINED;
|
||||
if(bytes_read > 0) {
|
||||
return_obj = mjs_mk_string(mjs, furi_string_get_cstr(rx_buf), bytes_read, true);
|
||||
}
|
||||
mjs_return(mjs, return_obj);
|
||||
furi_string_free(rx_buf);
|
||||
}
|
||||
|
||||
static void js_uart_read_bytes(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsUartInst* uart = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(uart);
|
||||
if(!uart->setup_done) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t read_len = 0;
|
||||
uint32_t timeout = FuriWaitForever;
|
||||
|
||||
do {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args == 1) {
|
||||
mjs_val_t arg = mjs_arg(mjs, 0);
|
||||
if(!mjs_is_number(arg)) {
|
||||
break;
|
||||
}
|
||||
read_len = mjs_get_int32(mjs, arg);
|
||||
} else if(num_args == 2) {
|
||||
mjs_val_t len_arg = mjs_arg(mjs, 0);
|
||||
mjs_val_t timeout_arg = mjs_arg(mjs, 1);
|
||||
if((!mjs_is_number(len_arg)) || (!mjs_is_number(timeout_arg))) {
|
||||
break;
|
||||
}
|
||||
read_len = mjs_get_int32(mjs, len_arg);
|
||||
timeout = mjs_get_int32(mjs, timeout_arg);
|
||||
}
|
||||
} while(0);
|
||||
|
||||
if(read_len == 0) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
char* read_buf = malloc(read_len);
|
||||
size_t bytes_read = js_uart_receive(uart, read_buf, read_len, timeout);
|
||||
|
||||
mjs_val_t return_obj = MJS_UNDEFINED;
|
||||
if(bytes_read > 0) {
|
||||
return_obj = mjs_mk_array_buf(mjs, read_buf, bytes_read);
|
||||
}
|
||||
mjs_return(mjs, return_obj);
|
||||
free(read_buf);
|
||||
}
|
||||
|
||||
static bool js_uart_expect_parse_string(struct mjs* mjs, mjs_val_t arg, PatternArray_t patterns) {
|
||||
size_t str_len = 0;
|
||||
const char* arg_str = mjs_get_string(mjs, &arg, &str_len);
|
||||
if((str_len == 0) || (arg_str == NULL)) {
|
||||
return false;
|
||||
}
|
||||
PatternArrayItem* item = PatternArray_push_new(patterns);
|
||||
item->data = malloc(str_len + 1);
|
||||
memcpy(item->data, arg_str, str_len);
|
||||
item->len = str_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool js_uart_expect_parse_array(struct mjs* mjs, mjs_val_t arg, PatternArray_t patterns) {
|
||||
size_t array_len = mjs_array_length(mjs, arg);
|
||||
if(array_len == 0) {
|
||||
return false;
|
||||
}
|
||||
char* array_data = malloc(array_len + 1);
|
||||
|
||||
for(size_t i = 0; i < array_len; i++) {
|
||||
mjs_val_t array_arg = mjs_array_get(mjs, arg, i);
|
||||
if(!mjs_is_number(array_arg)) {
|
||||
free(array_data);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t byte_val = mjs_get_int32(mjs, array_arg);
|
||||
if(byte_val > 0xFF) {
|
||||
free(array_data);
|
||||
return false;
|
||||
}
|
||||
array_data[i] = byte_val;
|
||||
}
|
||||
|
||||
PatternArrayItem* item = PatternArray_push_new(patterns);
|
||||
item->data = array_data;
|
||||
item->len = array_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
js_uart_expect_parse_args(struct mjs* mjs, PatternArray_t patterns, uint32_t* timeout) {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args == 2) {
|
||||
mjs_val_t timeout_arg = mjs_arg(mjs, 1);
|
||||
if(!mjs_is_number(timeout_arg)) {
|
||||
return false;
|
||||
}
|
||||
*timeout = mjs_get_int32(mjs, timeout_arg);
|
||||
} else if(num_args != 1) {
|
||||
return false;
|
||||
}
|
||||
mjs_val_t patterns_arg = mjs_arg(mjs, 0);
|
||||
if(mjs_is_string(patterns_arg)) { // Single string pattern
|
||||
if(!js_uart_expect_parse_string(mjs, patterns_arg, patterns)) {
|
||||
return false;
|
||||
}
|
||||
} else if(mjs_is_array(patterns_arg)) {
|
||||
size_t array_len = mjs_array_length(mjs, patterns_arg);
|
||||
if(array_len == 0) {
|
||||
return false;
|
||||
}
|
||||
mjs_val_t array_arg = mjs_array_get(mjs, patterns_arg, 0);
|
||||
|
||||
if(mjs_is_number(array_arg)) { // Binary array pattern
|
||||
if(!js_uart_expect_parse_array(mjs, patterns_arg, patterns)) {
|
||||
return false;
|
||||
}
|
||||
} else if((mjs_is_string(array_arg)) || (mjs_is_array(array_arg))) { // Multiple patterns
|
||||
for(size_t i = 0; i < array_len; i++) {
|
||||
mjs_val_t arg = mjs_array_get(mjs, patterns_arg, i);
|
||||
|
||||
if(mjs_is_string(arg)) {
|
||||
if(!js_uart_expect_parse_string(mjs, arg, patterns)) {
|
||||
return false;
|
||||
}
|
||||
} else if(mjs_is_array(arg)) {
|
||||
if(!js_uart_expect_parse_array(mjs, arg, patterns)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
js_uart_expect_check_pattern_start(PatternArray_t patterns, char value, int32_t pattern_last) {
|
||||
size_t array_len = PatternArray_size(patterns);
|
||||
if((pattern_last + 1) >= (int32_t)array_len) {
|
||||
return (-1);
|
||||
}
|
||||
for(size_t i = pattern_last + 1; i < array_len; i++) {
|
||||
if(PatternArray_get(patterns, i)->data[0] == value) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return (-1);
|
||||
}
|
||||
|
||||
static void js_uart_expect(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsUartInst* uart = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(uart);
|
||||
if(!uart->setup_done) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t timeout = FuriWaitForever;
|
||||
PatternArray_t patterns;
|
||||
PatternArray_it_t it;
|
||||
PatternArray_init(patterns);
|
||||
|
||||
if(!js_uart_expect_parse_args(mjs, patterns, &timeout)) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
for(PatternArray_it(it, patterns); !PatternArray_end_p(it); PatternArray_next(it)) {
|
||||
const PatternArrayItem* item = PatternArray_cref(it);
|
||||
free(item->data);
|
||||
}
|
||||
PatternArray_clear(patterns);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t pattern_len_max = 0;
|
||||
for(PatternArray_it(it, patterns); !PatternArray_end_p(it); PatternArray_next(it)) {
|
||||
const PatternArrayItem* item = PatternArray_cref(it);
|
||||
if(item->len > pattern_len_max) {
|
||||
pattern_len_max = item->len;
|
||||
}
|
||||
}
|
||||
|
||||
char* compare_buf = malloc(pattern_len_max);
|
||||
int32_t pattern_found = -1;
|
||||
int32_t pattern_candidate = -1;
|
||||
size_t buf_len = 0;
|
||||
bool is_timeout = false;
|
||||
|
||||
while(1) {
|
||||
if(buf_len == 0) {
|
||||
// Empty buffer - read by 1 byte to find pattern start
|
||||
size_t bytes_read = js_uart_receive(uart, &compare_buf[0], 1, timeout);
|
||||
if(bytes_read != 1) {
|
||||
is_timeout = true;
|
||||
break;
|
||||
}
|
||||
pattern_candidate = js_uart_expect_check_pattern_start(patterns, compare_buf[0], -1);
|
||||
if(pattern_candidate == -1) {
|
||||
continue;
|
||||
}
|
||||
buf_len = 1;
|
||||
}
|
||||
assert(pattern_candidate >= 0);
|
||||
|
||||
// Read next and try to find pattern match
|
||||
PatternArrayItem* pattern_cur = PatternArray_get(patterns, pattern_candidate);
|
||||
pattern_found = pattern_candidate;
|
||||
for(size_t i = 0; i < pattern_cur->len; i++) {
|
||||
if(i >= buf_len) {
|
||||
size_t bytes_read = js_uart_receive(uart, &compare_buf[i], 1, timeout);
|
||||
if(bytes_read != 1) {
|
||||
is_timeout = true;
|
||||
break;
|
||||
}
|
||||
buf_len++;
|
||||
}
|
||||
if(compare_buf[i] != pattern_cur->data[i]) {
|
||||
pattern_found = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if((is_timeout) || (pattern_found >= 0)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Search other patterns with the same start char
|
||||
pattern_candidate =
|
||||
js_uart_expect_check_pattern_start(patterns, compare_buf[0], pattern_candidate);
|
||||
if(pattern_candidate >= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for another pattern start
|
||||
for(size_t i = 1; i < buf_len; i++) {
|
||||
pattern_candidate = js_uart_expect_check_pattern_start(patterns, compare_buf[i], -1);
|
||||
if(pattern_candidate >= 0) {
|
||||
memmove(&compare_buf[0], &compare_buf[i], buf_len - i);
|
||||
buf_len -= i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(pattern_candidate >= 0) {
|
||||
continue;
|
||||
}
|
||||
// Nothing found - reset buffer
|
||||
buf_len = 0;
|
||||
}
|
||||
|
||||
if(is_timeout) {
|
||||
FURI_LOG_W(TAG, "Expect: timeout");
|
||||
}
|
||||
|
||||
for(PatternArray_it(it, patterns); !PatternArray_end_p(it); PatternArray_next(it)) {
|
||||
const PatternArrayItem* item = PatternArray_cref(it);
|
||||
free(item->data);
|
||||
}
|
||||
PatternArray_clear(patterns);
|
||||
free(compare_buf);
|
||||
|
||||
if(pattern_found >= 0) {
|
||||
mjs_return(mjs, mjs_mk_number(mjs, pattern_found));
|
||||
} else {
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
}
|
||||
|
||||
static void* js_uart_create(struct mjs* mjs, mjs_val_t* object) {
|
||||
JsUartInst* js_uart = malloc(sizeof(JsUartInst));
|
||||
js_uart->mjs = mjs;
|
||||
mjs_val_t uart_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, uart_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, js_uart));
|
||||
mjs_set(mjs, uart_obj, "setup", ~0, MJS_MK_FN(js_uart_setup));
|
||||
mjs_set(mjs, uart_obj, "write", ~0, MJS_MK_FN(js_uart_write));
|
||||
mjs_set(mjs, uart_obj, "read", ~0, MJS_MK_FN(js_uart_read));
|
||||
mjs_set(mjs, uart_obj, "readln", ~0, MJS_MK_FN(js_uart_readln));
|
||||
mjs_set(mjs, uart_obj, "readBytes", ~0, MJS_MK_FN(js_uart_read_bytes));
|
||||
mjs_set(mjs, uart_obj, "expect", ~0, MJS_MK_FN(js_uart_expect));
|
||||
*object = uart_obj;
|
||||
|
||||
return js_uart;
|
||||
}
|
||||
|
||||
static void js_uart_destroy(void* inst) {
|
||||
JsUartInst* js_uart = inst;
|
||||
if(js_uart->setup_done) {
|
||||
furi_hal_serial_async_rx_stop(js_uart->serial_handle);
|
||||
furi_hal_serial_deinit(js_uart->serial_handle);
|
||||
furi_hal_serial_control_release(js_uart->serial_handle);
|
||||
js_uart->serial_handle = NULL;
|
||||
}
|
||||
|
||||
furi_stream_buffer_free(js_uart->rx_stream);
|
||||
free(js_uart);
|
||||
}
|
||||
|
||||
static const JsModuleDescriptor js_uart_desc = {
|
||||
"uart",
|
||||
js_uart_create,
|
||||
js_uart_destroy,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor plugin_descriptor = {
|
||||
.appid = PLUGIN_APP_ID,
|
||||
.ep_api_version = PLUGIN_API_VERSION,
|
||||
.entry_point = &js_uart_desc,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* js_uart_ep(void) {
|
||||
return &plugin_descriptor;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <flipper_application/api_hashtable/api_hashtable.h>
|
||||
|
||||
/*
|
||||
* Resolver interface with private application's symbols.
|
||||
* Implementation is contained in app_api_table.c
|
||||
*/
|
||||
extern const ElfApiInterface* const application_api_interface;
|
||||
27
applications/system/js_app/plugin_api/app_api_table.cpp
Normal file
27
applications/system/js_app/plugin_api/app_api_table.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#include <flipper_application/api_hashtable/api_hashtable.h>
|
||||
#include <flipper_application/api_hashtable/compilesort.hpp>
|
||||
|
||||
/*
|
||||
* This file contains an implementation of a symbol table
|
||||
* with private app's symbols. It is used by composite API resolver
|
||||
* to load plugins that use internal application's APIs.
|
||||
*/
|
||||
#include "app_api_table_i.h"
|
||||
|
||||
static_assert(!has_hash_collisions(app_api_table), "Detected API method hash collision!");
|
||||
|
||||
constexpr HashtableApiInterface applicaton_hashtable_api_interface{
|
||||
{
|
||||
.api_version_major = 0,
|
||||
.api_version_minor = 0,
|
||||
/* generic resolver using pre-sorted array */
|
||||
.resolver_callback = &elf_resolve_from_hashtable,
|
||||
},
|
||||
/* pointers to application's API table boundaries */
|
||||
.table_cbegin = app_api_table.cbegin(),
|
||||
.table_cend = app_api_table.cend(),
|
||||
};
|
||||
|
||||
/* Casting to generic resolver to use in Composite API resolver */
|
||||
extern "C" const ElfApiInterface* const application_api_interface =
|
||||
&applicaton_hashtable_api_interface;
|
||||
11
applications/system/js_app/plugin_api/app_api_table_i.h
Normal file
11
applications/system/js_app/plugin_api/app_api_table_i.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#include <assets_icons.h>
|
||||
#include "js_plugin_api.h"
|
||||
/*
|
||||
* A list of app's private functions and objects to expose for plugins.
|
||||
* It is used to generate a table of symbols for import resolver to use.
|
||||
* TBD: automatically generate this table from app's header files
|
||||
*/
|
||||
static constexpr auto app_api_table = sort(create_array_t<sym_entry>(
|
||||
API_METHOD(js_delay_with_flags, bool, (struct mjs*, uint32_t)),
|
||||
API_METHOD(js_flags_set, void, (struct mjs*, uint32_t)),
|
||||
API_METHOD(js_flags_wait, uint32_t, (struct mjs*, uint32_t, uint32_t))));
|
||||
18
applications/system/js_app/plugin_api/js_plugin_api.h
Normal file
18
applications/system/js_app/plugin_api/js_plugin_api.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <mjs_core_public.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
bool js_delay_with_flags(struct mjs* mjs, uint32_t time);
|
||||
|
||||
void js_flags_set(struct mjs* mjs, uint32_t flags);
|
||||
|
||||
uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags, uint32_t timeout);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
43
applications/system/js_app/views/console_font.h
Normal file
43
applications/system/js_app/views/console_font.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
Fontname: -misc-spleen-medium-r-normal--8-80-72-72-C-50-ISO10646-1
|
||||
Copyright: Copyright (c) 2018-2022, Frederic Cambus
|
||||
Glyphs: 96/472
|
||||
BBX Build Mode: 2
|
||||
*/
|
||||
static const uint8_t u8g2_font_spleen5x8_mr[] =
|
||||
"`\2\3\2\3\4\1\1\4\5\10\0\377\6\377\7\377\1\77\2\217\3\325 \6\305\372\274\2!\10\305"
|
||||
"Zaw(\7\42\12\305:\245$JrV\0#\15\305\332I\62(\245$\31\224\62\0$\13\305Z"
|
||||
"\331R\23\65e\214\0%\15\305zI\224\24\263\60)%!\0&\16\305ZY\22%\221\224$R\244"
|
||||
"\244\0'\7\305Za\235\31(\10\305z\215\255\25\0)\10\305:i\261\255\6*\13\305\372X\24I"
|
||||
"C$\225\1+\12\305\372h\30\15R\230\3,\10\305\372\314a\226\1-\10\305\372\344!'\1.\7"
|
||||
"\305\372\34s\0/\13\305za\26fa\26\206\0\60\12\305\332R%\261\224\42\35\61\10\305\372\231\330"
|
||||
"\66\3\62\12\305\332R\61\222\302!\6\63\12\305\332R-M\242H\7\64\14\305\272a\22%\321\220\205"
|
||||
"\71\0\65\12\305\272C\22\256a\262\3\66\12\305\332R\70U\242H\7\67\13\305\272C\22\205Y\61G"
|
||||
"\0\70\12\305\332RI\252D\221\16\71\12\305\332R%\212\306H\7:\10\305\372\264\34\317\1;\11\305"
|
||||
"\372\264\34\12\263\14<\11\305\372HVL\313\0=\11\305\372\224!\36r\20>\11\305\332i\61\253#"
|
||||
"\0\77\12\305:R\61\253C\71\2@\13\305\332R%Q\22%\235\1A\14\305\332R%J\206$J"
|
||||
"\242\30B\12\305\272Se\252D\311\16C\10\305\332K\330:\3D\14\305\272S%J\242$Jv\0"
|
||||
"E\11\305\332K\70\205\351\14F\12\305\332K\30Na\16\1G\14\305\332K\230(Q\22E\63\0H"
|
||||
"\16\305\272Q\22%C\22%Q\22\305\0I\10\305\332[\330\66\3J\11\305\332[\330\244#\0K\14"
|
||||
"\305\272Q\22%S%J\242\30L\7\305\272a\327\31M\16\305\272Q\62$C\22%Q\22\305\0N"
|
||||
"\15\305\272Q\242$JEI\224(\6O\14\305\332R%J\242$\212t\0P\13\305\272S%J\246"
|
||||
"\60\207\0Q\14\305\332R%J\242$\212D\5R\13\305\272S%J\246J\24\3S\11\305\332K\252"
|
||||
"\206\311\16T\10\305\272\203\24v\7U\15\305\272Q\22%Q\22%Q\64\3V\14\305\272Q\22%Q"
|
||||
"\22E\232\16W\16\305\272Q\22%Q\62$C\22\305\0X\14\305\272Q\22E\232T\211b\0Y\14"
|
||||
"\305\272Q\22%Q\64&;\0Z\12\305\272C\230\65\16\61\0[\10\305:S\330\343\2\134\13\305\32"
|
||||
"a\32\246a\32&\0]\10\305:c\237\26\0^\11\305\372YR\313\311\0_\7\305\372\334\207\4`"
|
||||
"\7\305:i\316\21a\12\305\372\240\32-Q\64\3b\14\305\32a\70U\242$Jv\0c\11\305\372"
|
||||
"\340\22Vg\0d\14\305za\264DI\224D\321\14e\13\305\372\340\22%C\222\316\0f\12\305Z"
|
||||
"R\230ma\35\1g\14\305\372\340\22%Q\244&\23\0h\14\305\32a\70U\242$J\242\30i\11"
|
||||
"\305\372\71\42\26e\0j\11\305\372\71\24\66i\0k\13\305\32a))iIT\6l\10\305:a"
|
||||
"\257\62\0m\15\305\372X\224\14\311\220DI\24\3n\14\305\372\330T\211\222(\211b\0o\13\305\372"
|
||||
"\240T\211\222(\322\1p\13\305\372\330T\211\222)\14\1q\13\305\372\340\22%Q\64V\0r\12\305"
|
||||
"\372\340\22%a\35\2s\11\305\372\340\222\252\311\16t\11\305:a\266\205U\31u\14\305\372X\224D"
|
||||
"I\224D\321\14v\14\305\372X\224DI\24i:\0w\15\305\372X\224D\311\220\14I\24\3x\13"
|
||||
"\305\372X\24iR%\212\1y\14\305\372X\224DI\24\215\311\4z\12\305\372\330\20f\265!\6{"
|
||||
"\12\305ZR\230\31\253\12\0|\7\305Za\77\1}\13\305\32j\30jZ\30i\0~\11\305\372\244"
|
||||
"H\321I\0\177\6\305\372\274\2\0\0\0\4\377\377\0";
|
||||
164
applications/system/js_app/views/console_view.c
Normal file
164
applications/system/js_app/views/console_view.c
Normal file
@@ -0,0 +1,164 @@
|
||||
#include "../js_app_i.h"
|
||||
#include "console_font.h"
|
||||
|
||||
#define CONSOLE_LINES 8
|
||||
#define CONSOLE_CHAR_W 5
|
||||
#define LINE_BREAKS_MAX 3
|
||||
#define LINE_LEN_MAX (128 / CONSOLE_CHAR_W)
|
||||
|
||||
struct JsConsoleView {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
FuriString* text[CONSOLE_LINES];
|
||||
} JsConsoleViewModel;
|
||||
|
||||
static void console_view_draw_callback(Canvas* canvas, void* _model) {
|
||||
JsConsoleViewModel* model = _model;
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_set_custom_u8g2_font(canvas, u8g2_font_spleen5x8_mr);
|
||||
uint8_t line_h = canvas_current_font_height(canvas);
|
||||
|
||||
for(size_t i = 0; i < CONSOLE_LINES; i++) {
|
||||
canvas_draw_str(canvas, 0, (i + 1) * line_h - 1, furi_string_get_cstr(model->text[i]));
|
||||
if(furi_string_size(model->text[i]) > LINE_LEN_MAX) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 128 - 7, (i + 1) * line_h - 1, "...");
|
||||
canvas_set_custom_u8g2_font(canvas, u8g2_font_spleen5x8_mr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool console_view_input_callback(InputEvent* event, void* context) {
|
||||
UNUSED(event);
|
||||
UNUSED(context);
|
||||
return false;
|
||||
}
|
||||
|
||||
void console_view_push_line(JsConsoleView* console_view, const char* text, bool line_trimmed) {
|
||||
with_view_model(
|
||||
console_view->view,
|
||||
JsConsoleViewModel * model,
|
||||
{
|
||||
FuriString* str_temp = model->text[0];
|
||||
for(size_t i = 0; i < CONSOLE_LINES - 1; i++) {
|
||||
model->text[i] = model->text[i + 1];
|
||||
}
|
||||
if(!line_trimmed) {
|
||||
furi_string_printf(str_temp, "%.*s", LINE_LEN_MAX, text);
|
||||
} else {
|
||||
// Leave some space for dots
|
||||
furi_string_printf(str_temp, "%.*s ", LINE_LEN_MAX - 1, text);
|
||||
}
|
||||
model->text[CONSOLE_LINES - 1] = str_temp;
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void console_view_print(JsConsoleView* console_view, const char* text) {
|
||||
char line_buf[LINE_LEN_MAX + 1];
|
||||
uint8_t line_buf_cnt = 0;
|
||||
uint8_t utf8_bytes_left = 0;
|
||||
uint8_t line_break_cnt = 0;
|
||||
bool line_trim = false;
|
||||
|
||||
for(size_t i = 0; i < strlen(text); i++) {
|
||||
if(text[i] & 0x80) { // UTF8 or another non-ascii character byte
|
||||
if(utf8_bytes_left > 0) {
|
||||
utf8_bytes_left--;
|
||||
if(utf8_bytes_left == 0) {
|
||||
line_buf[line_buf_cnt++] = '?';
|
||||
}
|
||||
} else {
|
||||
if((text[i] & 0xE0) == 0xC0) {
|
||||
utf8_bytes_left = 1;
|
||||
} else if((text[i] & 0xF0) == 0xE0) {
|
||||
utf8_bytes_left = 2;
|
||||
} else if((text[i] & 0xF8) == 0xF0) {
|
||||
utf8_bytes_left = 3;
|
||||
} else {
|
||||
line_buf[line_buf_cnt++] = '?';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(utf8_bytes_left > 0) {
|
||||
utf8_bytes_left = 0;
|
||||
line_buf[line_buf_cnt++] = '?';
|
||||
if(line_buf_cnt >= LINE_LEN_MAX) {
|
||||
line_break_cnt++;
|
||||
if(line_break_cnt >= LINE_BREAKS_MAX) {
|
||||
line_trim = true;
|
||||
break;
|
||||
}
|
||||
line_buf[line_buf_cnt] = '\0';
|
||||
console_view_push_line(console_view, line_buf, false);
|
||||
line_buf_cnt = 1;
|
||||
line_buf[0] = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
if(text[i] == '\n') {
|
||||
line_buf[line_buf_cnt] = '\0';
|
||||
line_buf_cnt = 0;
|
||||
console_view_push_line(console_view, line_buf, false);
|
||||
} else {
|
||||
line_buf[line_buf_cnt++] = text[i];
|
||||
}
|
||||
|
||||
if(line_buf_cnt >= LINE_LEN_MAX) {
|
||||
line_break_cnt++;
|
||||
if(line_break_cnt >= LINE_BREAKS_MAX) {
|
||||
line_trim = true;
|
||||
break;
|
||||
}
|
||||
line_buf[line_buf_cnt] = '\0';
|
||||
console_view_push_line(console_view, line_buf, false);
|
||||
line_buf_cnt = 1;
|
||||
line_buf[0] = ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
if(line_buf_cnt > 0) {
|
||||
line_buf[line_buf_cnt] = '\0';
|
||||
console_view_push_line(console_view, line_buf, line_trim);
|
||||
}
|
||||
}
|
||||
|
||||
JsConsoleView* console_view_alloc(void) {
|
||||
JsConsoleView* console_view = malloc(sizeof(JsConsoleView));
|
||||
console_view->view = view_alloc();
|
||||
view_set_draw_callback(console_view->view, console_view_draw_callback);
|
||||
view_set_input_callback(console_view->view, console_view_input_callback);
|
||||
view_allocate_model(console_view->view, ViewModelTypeLocking, sizeof(JsConsoleViewModel));
|
||||
|
||||
with_view_model(
|
||||
console_view->view,
|
||||
JsConsoleViewModel * model,
|
||||
{
|
||||
for(size_t i = 0; i < CONSOLE_LINES; i++) {
|
||||
model->text[i] = furi_string_alloc();
|
||||
}
|
||||
},
|
||||
true);
|
||||
return console_view;
|
||||
}
|
||||
|
||||
void console_view_free(JsConsoleView* console_view) {
|
||||
with_view_model(
|
||||
console_view->view,
|
||||
JsConsoleViewModel * model,
|
||||
{
|
||||
for(size_t i = 0; i < CONSOLE_LINES; i++) {
|
||||
furi_string_free(model->text[i]);
|
||||
}
|
||||
},
|
||||
false);
|
||||
view_free(console_view->view);
|
||||
free(console_view);
|
||||
}
|
||||
|
||||
View* console_view_get_view(JsConsoleView* console_view) {
|
||||
return console_view->view;
|
||||
}
|
||||
13
applications/system/js_app/views/console_view.h
Normal file
13
applications/system/js_app/views/console_view.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct JsConsoleView JsConsoleView;
|
||||
|
||||
JsConsoleView* console_view_alloc(void);
|
||||
|
||||
void console_view_free(JsConsoleView* console_view);
|
||||
|
||||
View* console_view_get_view(JsConsoleView* console_view);
|
||||
|
||||
void console_view_print(JsConsoleView* console_view, const char* text);
|
||||
Reference in New Issue
Block a user