mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-12 20:49:49 +04:00
Merge remote-tracking branch 'OFW/dev' into dev
This commit is contained in:
@@ -12,4 +12,4 @@ tests.assert_eq(false, doesSdkSupport(["abobus", "other-nonexistent-feature"]));
|
|||||||
|
|
||||||
tests.assert_eq("flipperdevices", flipper.firmwareVendor);
|
tests.assert_eq("flipperdevices", flipper.firmwareVendor);
|
||||||
tests.assert_eq(0, flipper.jsSdkVersion[0]);
|
tests.assert_eq(0, flipper.jsSdkVersion[0]);
|
||||||
tests.assert_eq(1, flipper.jsSdkVersion[1]);
|
tests.assert_eq(2, flipper.jsSdkVersion[1]);
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ static void mf_ultralight_reader_test(const char* path) {
|
|||||||
nfc_listener_start(mfu_listener, NULL, NULL);
|
nfc_listener_start(mfu_listener, NULL, NULL);
|
||||||
|
|
||||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||||
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
|
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL);
|
||||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
||||||
|
|
||||||
nfc_listener_stop(mfu_listener);
|
nfc_listener_stop(mfu_listener);
|
||||||
@@ -315,7 +315,7 @@ MU_TEST(ntag_213_locked_reader) {
|
|||||||
nfc_listener_start(mfu_listener, NULL, NULL);
|
nfc_listener_start(mfu_listener, NULL, NULL);
|
||||||
|
|
||||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||||
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
|
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL);
|
||||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
||||||
|
|
||||||
nfc_listener_stop(mfu_listener);
|
nfc_listener_stop(mfu_listener);
|
||||||
@@ -353,7 +353,7 @@ static void mf_ultralight_write(void) {
|
|||||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||||
|
|
||||||
// Initial read
|
// Initial read
|
||||||
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
|
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL);
|
||||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
||||||
|
|
||||||
mu_assert(
|
mu_assert(
|
||||||
@@ -371,7 +371,7 @@ static void mf_ultralight_write(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verification read
|
// Verification read
|
||||||
error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
|
error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL);
|
||||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
||||||
|
|
||||||
nfc_listener_stop(mfu_listener);
|
nfc_listener_stop(mfu_listener);
|
||||||
|
|||||||
@@ -110,6 +110,23 @@ App(
|
|||||||
fap_libs=["assets"],
|
fap_libs=["assets"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="js_gui__widget",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="js_view_widget_ep",
|
||||||
|
requires=["js_app"],
|
||||||
|
sources=["modules/js_gui/widget.c"],
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="js_gui__icon",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="js_gui_icon_ep",
|
||||||
|
requires=["js_app"],
|
||||||
|
sources=["modules/js_gui/icon.c"],
|
||||||
|
fap_libs=["assets"],
|
||||||
|
)
|
||||||
|
|
||||||
App(
|
App(
|
||||||
appid="js_notification",
|
appid="js_notification",
|
||||||
apptype=FlipperAppType.PLUGIN,
|
apptype=FlipperAppType.PLUGIN,
|
||||||
@@ -158,14 +175,6 @@ App(
|
|||||||
sources=["modules/js_storage.c"],
|
sources=["modules/js_storage.c"],
|
||||||
)
|
)
|
||||||
|
|
||||||
App(
|
|
||||||
appid="js_widget",
|
|
||||||
apptype=FlipperAppType.PLUGIN,
|
|
||||||
entry_point="js_widget_ep",
|
|
||||||
requires=["js_app"],
|
|
||||||
sources=["modules/js_widget.c"],
|
|
||||||
)
|
|
||||||
|
|
||||||
App(
|
App(
|
||||||
appid="js_vgm",
|
appid="js_vgm",
|
||||||
apptype=FlipperAppType.PLUGIN,
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ let gpio = require("gpio");
|
|||||||
|
|
||||||
// initialize pins
|
// initialize pins
|
||||||
let led = gpio.get("pc3"); // same as `gpio.get(7)`
|
let led = gpio.get("pc3"); // same as `gpio.get(7)`
|
||||||
|
let led2 = gpio.get("pa7"); // same as `gpio.get(2)`
|
||||||
let pot = gpio.get("pc0"); // same as `gpio.get(16)`
|
let pot = gpio.get("pc0"); // same as `gpio.get(16)`
|
||||||
let button = gpio.get("pc1"); // same as `gpio.get(15)`
|
let button = gpio.get("pc1"); // same as `gpio.get(15)`
|
||||||
led.init({ direction: "out", outMode: "push_pull" });
|
led.init({ direction: "out", outMode: "push_pull" });
|
||||||
@@ -16,6 +17,13 @@ eventLoop.subscribe(eventLoop.timer("periodic", 1000), function (_, _item, led,
|
|||||||
return [led, !state];
|
return [led, !state];
|
||||||
}, led, true);
|
}, led, true);
|
||||||
|
|
||||||
|
// cycle led pwm
|
||||||
|
print("Commencing PWM (PA7)");
|
||||||
|
eventLoop.subscribe(eventLoop.timer("periodic", 10), function (_, _item, led2, state) {
|
||||||
|
led2.pwmWrite(10000, state);
|
||||||
|
return [led2, (state + 1) % 101];
|
||||||
|
}, led2, 0);
|
||||||
|
|
||||||
// read potentiometer when button is pressed
|
// read potentiometer when button is pressed
|
||||||
print("Press the button (PC1)");
|
print("Press the button (PC1)");
|
||||||
eventLoop.subscribe(button.interrupt(), function (_, _item, pot) {
|
eventLoop.subscribe(button.interrupt(), function (_, _item, pot) {
|
||||||
|
|||||||
@@ -9,8 +9,23 @@ let byteInputView = require("gui/byte_input");
|
|||||||
let textBoxView = require("gui/text_box");
|
let textBoxView = require("gui/text_box");
|
||||||
let dialogView = require("gui/dialog");
|
let dialogView = require("gui/dialog");
|
||||||
let filePicker = require("gui/file_picker");
|
let filePicker = require("gui/file_picker");
|
||||||
|
let widget = require("gui/widget");
|
||||||
|
let icon = require("gui/icon");
|
||||||
let flipper = require("flipper");
|
let flipper = require("flipper");
|
||||||
|
|
||||||
|
// declare clock widget children
|
||||||
|
let cuteDolphinWithWatch = icon.getBuiltin("DolphinWait_59x54");
|
||||||
|
let jsLogo = icon.getBuiltin("js_script_10px");
|
||||||
|
let stopwatchWidgetElements = [
|
||||||
|
{ element: "string", x: 67, y: 44, align: "bl", font: "big_numbers", text: "00 00" },
|
||||||
|
{ element: "string", x: 77, y: 22, align: "bl", font: "primary", text: "Stopwatch" },
|
||||||
|
{ element: "frame", x: 64, y: 27, w: 28, h: 20, radius: 3 },
|
||||||
|
{ element: "frame", x: 100, y: 27, w: 28, h: 20, radius: 3 },
|
||||||
|
{ element: "icon", x: 0, y: 5, iconData: cuteDolphinWithWatch },
|
||||||
|
{ element: "icon", x: 64, y: 13, iconData: jsLogo },
|
||||||
|
{ element: "button", button: "right", text: "Back" },
|
||||||
|
];
|
||||||
|
|
||||||
// declare view instances
|
// declare view instances
|
||||||
let views = {
|
let views = {
|
||||||
loading: loadingView.make(),
|
loading: loadingView.make(),
|
||||||
@@ -31,6 +46,7 @@ let views = {
|
|||||||
longText: textBoxView.makeWith({
|
longText: textBoxView.makeWith({
|
||||||
text: "This is a very long string that demonstrates the TextBox view. Use the D-Pad to scroll backwards and forwards.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rhoncus est malesuada quam egestas ultrices. Maecenas non eros a nulla eleifend vulputate et ut risus. Quisque in mauris mattis, venenatis risus eget, aliquam diam. Fusce pretium feugiat mauris, ut faucibus ex volutpat in. Phasellus volutpat ex sed gravida consectetur. Aliquam sed lectus feugiat, tristique lectus et, bibendum lacus. Ut sit amet augue eu sapien elementum aliquam quis vitae tortor. Vestibulum quis commodo odio. In elementum fermentum massa, eu pellentesque nibh cursus at. Integer eleifend lacus nec purus elementum sodales. Nulla elementum neque urna, non vulputate massa semper sed. Fusce ut nisi vitae dui blandit congue pretium vitae turpis.",
|
text: "This is a very long string that demonstrates the TextBox view. Use the D-Pad to scroll backwards and forwards.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rhoncus est malesuada quam egestas ultrices. Maecenas non eros a nulla eleifend vulputate et ut risus. Quisque in mauris mattis, venenatis risus eget, aliquam diam. Fusce pretium feugiat mauris, ut faucibus ex volutpat in. Phasellus volutpat ex sed gravida consectetur. Aliquam sed lectus feugiat, tristique lectus et, bibendum lacus. Ut sit amet augue eu sapien elementum aliquam quis vitae tortor. Vestibulum quis commodo odio. In elementum fermentum massa, eu pellentesque nibh cursus at. Integer eleifend lacus nec purus elementum sodales. Nulla elementum neque urna, non vulputate massa semper sed. Fusce ut nisi vitae dui blandit congue pretium vitae turpis.",
|
||||||
}),
|
}),
|
||||||
|
stopwatchWidget: widget.makeWith({}, stopwatchWidgetElements),
|
||||||
demos: submenuView.makeWith({
|
demos: submenuView.makeWith({
|
||||||
header: "Choose a demo",
|
header: "Choose a demo",
|
||||||
items: [
|
items: [
|
||||||
@@ -40,6 +56,7 @@ let views = {
|
|||||||
"Byte input",
|
"Byte input",
|
||||||
"Text box",
|
"Text box",
|
||||||
"File picker",
|
"File picker",
|
||||||
|
"Widget",
|
||||||
"Exit app",
|
"Exit app",
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@@ -72,6 +89,8 @@ eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, v
|
|||||||
views.helloDialog.set("center", "Nice!");
|
views.helloDialog.set("center", "Nice!");
|
||||||
gui.viewDispatcher.switchTo(views.helloDialog);
|
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||||
} else if (index === 6) {
|
} else if (index === 6) {
|
||||||
|
gui.viewDispatcher.switchTo(views.stopwatchWidget);
|
||||||
|
} else if (index === 7) {
|
||||||
eventLoop.stop();
|
eventLoop.stop();
|
||||||
}
|
}
|
||||||
}, gui, eventLoop, views);
|
}, gui, eventLoop, views);
|
||||||
@@ -111,6 +130,31 @@ eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, gui, views
|
|||||||
gui.viewDispatcher.switchTo(views.demos);
|
gui.viewDispatcher.switchTo(views.demos);
|
||||||
}, gui, views, eventLoop);
|
}, gui, views, eventLoop);
|
||||||
|
|
||||||
|
// go to the demo chooser screen when the right key is pressed on the widget screen
|
||||||
|
eventLoop.subscribe(views.stopwatchWidget.button, function (_sub, buttonId, gui, views) {
|
||||||
|
if (buttonId === "right")
|
||||||
|
gui.viewDispatcher.switchTo(views.demos);
|
||||||
|
}, gui, views);
|
||||||
|
|
||||||
|
// count time
|
||||||
|
eventLoop.subscribe(eventLoop.timer("periodic", 500), function (_sub, _item, views, stopwatchWidgetElements, halfSeconds) {
|
||||||
|
let text = (halfSeconds / 2 / 60).toString();
|
||||||
|
if (halfSeconds < 10 * 60 * 2)
|
||||||
|
text = "0" + text;
|
||||||
|
|
||||||
|
text += (halfSeconds % 2 === 0) ? ":" : " ";
|
||||||
|
|
||||||
|
if (((halfSeconds / 2) % 60) < 10)
|
||||||
|
text += "0";
|
||||||
|
text += ((halfSeconds / 2) % 60).toString();
|
||||||
|
|
||||||
|
stopwatchWidgetElements[0].text = text;
|
||||||
|
views.stopwatchWidget.setChildren(stopwatchWidgetElements);
|
||||||
|
|
||||||
|
halfSeconds++;
|
||||||
|
return [views, stopwatchWidgetElements, halfSeconds];
|
||||||
|
}, views, stopwatchWidgetElements, 0);
|
||||||
|
|
||||||
// run UI
|
// run UI
|
||||||
gui.viewDispatcher.switchTo(views.demos);
|
gui.viewDispatcher.switchTo(views.demos);
|
||||||
eventLoop.run();
|
eventLoop.run();
|
||||||
|
|||||||
Binary file not shown.
@@ -1,62 +0,0 @@
|
|||||||
// Script cannot work without widget module so check before
|
|
||||||
checkSdkFeatures(["widget"]);
|
|
||||||
|
|
||||||
let widget = require("widget");
|
|
||||||
|
|
||||||
let demo_seconds = 30;
|
|
||||||
|
|
||||||
print("Loading file", __filename);
|
|
||||||
print("From directory", __dirname);
|
|
||||||
|
|
||||||
// addText supports "Primary" and "Secondary" font sizes.
|
|
||||||
widget.addText(10, 10, "Primary", "Example JS widget");
|
|
||||||
widget.addText(10, 20, "Secondary", "Example widget from JS!");
|
|
||||||
|
|
||||||
// load a Xbm file from the same directory as this script.
|
|
||||||
widget.addText(0, 30, "Secondary", __filename);
|
|
||||||
let logo = widget.loadImageXbm(__dirname + "/widget-js.fxbm");
|
|
||||||
|
|
||||||
// add a line (x1, y1, x2, y2)
|
|
||||||
widget.addLine(10, 35, 120, 35);
|
|
||||||
|
|
||||||
// add a circle/disc (x, y, radius)
|
|
||||||
widget.addCircle(12, 52, 10);
|
|
||||||
widget.addDisc(12, 52, 5);
|
|
||||||
|
|
||||||
// add a frame/box (x, y, width, height)
|
|
||||||
widget.addFrame(30, 45, 10, 10);
|
|
||||||
widget.addBox(32, 47, 6, 6);
|
|
||||||
|
|
||||||
// add a rounded frame/box (x, y, width, height, radius)
|
|
||||||
widget.addRframe(50, 45, 15, 15, 3);
|
|
||||||
widget.addRbox(53, 48, 6, 6, 2);
|
|
||||||
|
|
||||||
// add a dot (x, y)
|
|
||||||
widget.addDot(100, 45);
|
|
||||||
widget.addDot(102, 44);
|
|
||||||
widget.addDot(104, 43);
|
|
||||||
|
|
||||||
// add a glyph (x, y, glyph)
|
|
||||||
widget.addGlyph(115, 50, "#".charCodeAt(0));
|
|
||||||
|
|
||||||
// Show the widget (drawing the layers in the orderer they were added)
|
|
||||||
widget.show();
|
|
||||||
|
|
||||||
let i = 1;
|
|
||||||
let bitmap = undefined;
|
|
||||||
while (widget.isOpen() && i <= demo_seconds) {
|
|
||||||
// Print statements will only show up once the widget is closed.
|
|
||||||
print("count is at", i++);
|
|
||||||
|
|
||||||
// You can call remove on any added item, it does not impact the other ids.
|
|
||||||
if (bitmap) { widget.remove(bitmap); bitmap = undefined; }
|
|
||||||
// All of the addXXX functions return an id that can be used to remove the item.
|
|
||||||
else { bitmap = widget.addXbm(77, 45, logo); }
|
|
||||||
|
|
||||||
delay(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If user did not press the back button, close the widget.
|
|
||||||
if (widget.isOpen()) {
|
|
||||||
widget.close();
|
|
||||||
}
|
|
||||||
@@ -267,6 +267,8 @@ void js_check_sdk_compatibility(struct mjs* mjs) {
|
|||||||
|
|
||||||
static const char* extra_features[] = {
|
static const char* extra_features[] = {
|
||||||
"baseline", // dummy "feature"
|
"baseline", // dummy "feature"
|
||||||
|
"gpio-pwm",
|
||||||
|
"gui-widget",
|
||||||
|
|
||||||
// extra modules
|
// extra modules
|
||||||
"blebeacon",
|
"blebeacon",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
#define JS_SDK_VENDOR_FIRMWARE "unleashed"
|
#define JS_SDK_VENDOR_FIRMWARE "unleashed"
|
||||||
#define JS_SDK_VENDOR "flipperdevices"
|
#define JS_SDK_VENDOR "flipperdevices"
|
||||||
#define JS_SDK_MAJOR 0
|
#define JS_SDK_MAJOR 0
|
||||||
#define JS_SDK_MINOR 1
|
#define JS_SDK_MINOR 2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the foreign pointer in `obj["_"]`
|
* @brief Returns the foreign pointer in `obj["_"]`
|
||||||
@@ -255,6 +255,18 @@ static inline void
|
|||||||
return; \
|
return; \
|
||||||
} while(0)
|
} while(0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Prepends an error, sets the JS return value to `undefined` and returns
|
||||||
|
* a value C function
|
||||||
|
* @warning This macro executes `return;` by design
|
||||||
|
*/
|
||||||
|
#define JS_ERROR_AND_RETURN_VAL(mjs, error_code, ret_val, ...) \
|
||||||
|
do { \
|
||||||
|
mjs_prepend_errorf(mjs, error_code, __VA_ARGS__); \
|
||||||
|
mjs_return(mjs, MJS_UNDEFINED); \
|
||||||
|
return ret_val; \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
typedef struct JsModules JsModules;
|
typedef struct JsModules JsModules;
|
||||||
|
|
||||||
typedef void* (*JsModuleConstructor)(struct mjs* mjs, mjs_val_t* object, JsModules* modules);
|
typedef void* (*JsModuleConstructor)(struct mjs* mjs, mjs_val_t* object, JsModules* modules);
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ static void js_console_debug(struct mjs* mjs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void js_exit_flag_poll(struct mjs* mjs) {
|
static void js_exit_flag_poll(struct mjs* mjs) {
|
||||||
uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, 0);
|
uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny | FuriFlagNoClear, 0);
|
||||||
if(flags & FuriFlagError) {
|
if(flags & FuriFlagError) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -102,7 +102,8 @@ static void js_exit_flag_poll(struct mjs* mjs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool js_delay_with_flags(struct mjs* mjs, uint32_t time) {
|
bool js_delay_with_flags(struct mjs* mjs, uint32_t time) {
|
||||||
uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, time);
|
uint32_t flags =
|
||||||
|
furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny | FuriFlagNoClear, time);
|
||||||
if(flags & FuriFlagError) {
|
if(flags & FuriFlagError) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -124,7 +125,7 @@ uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags_mask, uint32_t timeout) {
|
|||||||
uint32_t flags = furi_thread_flags_get();
|
uint32_t flags = furi_thread_flags_get();
|
||||||
furi_check((flags & FuriFlagError) == 0);
|
furi_check((flags & FuriFlagError) == 0);
|
||||||
if(flags == 0) {
|
if(flags == 0) {
|
||||||
flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout);
|
flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny | FuriFlagNoClear, timeout);
|
||||||
} else {
|
} else {
|
||||||
uint32_t state = furi_thread_flags_clear(flags & flags_mask);
|
uint32_t state = furi_thread_flags_clear(flags & flags_mask);
|
||||||
furi_check((state & FuriFlagError) == 0);
|
furi_check((state & FuriFlagError) == 0);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
* @brief Context passed to the generic event callback
|
* @brief Context passed to the generic event callback
|
||||||
*/
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
FuriEventLoop* event_loop;
|
||||||
JsEventLoopObjectType object_type;
|
JsEventLoopObjectType object_type;
|
||||||
|
|
||||||
struct mjs* mjs;
|
struct mjs* mjs;
|
||||||
@@ -36,11 +37,6 @@ typedef struct {
|
|||||||
void* subscriptions; // SubscriptionArray_t, which we can't reference in this definition
|
void* subscriptions; // SubscriptionArray_t, which we can't reference in this definition
|
||||||
} JsEventLoopSubscription;
|
} JsEventLoopSubscription;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
FuriEventLoop* loop;
|
|
||||||
struct mjs* mjs;
|
|
||||||
} JsEventLoopTickContext;
|
|
||||||
|
|
||||||
ARRAY_DEF(SubscriptionArray, JsEventLoopSubscription*, M_PTR_OPLIST); //-V575
|
ARRAY_DEF(SubscriptionArray, JsEventLoopSubscription*, M_PTR_OPLIST); //-V575
|
||||||
ARRAY_DEF(ContractArray, JsEventLoopContract*, M_PTR_OPLIST); //-V575
|
ARRAY_DEF(ContractArray, JsEventLoopContract*, M_PTR_OPLIST); //-V575
|
||||||
|
|
||||||
@@ -51,7 +47,6 @@ struct JsEventLoop {
|
|||||||
FuriEventLoop* loop;
|
FuriEventLoop* loop;
|
||||||
SubscriptionArray_t subscriptions;
|
SubscriptionArray_t subscriptions;
|
||||||
ContractArray_t owned_contracts; //<! Contracts that were produced by this module
|
ContractArray_t owned_contracts; //<! Contracts that were produced by this module
|
||||||
JsEventLoopTickContext* tick_context;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,7 +55,7 @@ struct JsEventLoop {
|
|||||||
static void js_event_loop_callback_generic(void* param) {
|
static void js_event_loop_callback_generic(void* param) {
|
||||||
JsEventLoopCallbackContext* context = param;
|
JsEventLoopCallbackContext* context = param;
|
||||||
mjs_val_t result;
|
mjs_val_t result;
|
||||||
mjs_apply(
|
mjs_err_t error = mjs_apply(
|
||||||
context->mjs,
|
context->mjs,
|
||||||
&result,
|
&result,
|
||||||
context->callback,
|
context->callback,
|
||||||
@@ -68,6 +63,12 @@ static void js_event_loop_callback_generic(void* param) {
|
|||||||
context->arity,
|
context->arity,
|
||||||
context->arguments);
|
context->arguments);
|
||||||
|
|
||||||
|
bool is_error = strcmp(mjs_strerror(context->mjs, error), "NO_ERROR") != 0;
|
||||||
|
bool asked_to_stop = js_flags_wait(context->mjs, ThreadEventStop, 0) & ThreadEventStop;
|
||||||
|
if(is_error || asked_to_stop) {
|
||||||
|
furi_event_loop_stop(context->event_loop);
|
||||||
|
}
|
||||||
|
|
||||||
// save returned args for next call
|
// save returned args for next call
|
||||||
if(mjs_array_length(context->mjs, result) != context->arity - SYSTEM_ARGS) return;
|
if(mjs_array_length(context->mjs, result) != context->arity - SYSTEM_ARGS) return;
|
||||||
for(size_t i = 0; i < context->arity - SYSTEM_ARGS; i++) {
|
for(size_t i = 0; i < context->arity - SYSTEM_ARGS; i++) {
|
||||||
@@ -111,11 +112,14 @@ static void js_event_loop_subscription_cancel(struct mjs* mjs) {
|
|||||||
JsEventLoopSubscription* subscription = JS_GET_CONTEXT(mjs);
|
JsEventLoopSubscription* subscription = JS_GET_CONTEXT(mjs);
|
||||||
|
|
||||||
if(subscription->object_type == JsEventLoopObjectTypeTimer) {
|
if(subscription->object_type == JsEventLoopObjectTypeTimer) {
|
||||||
|
// timer operations are deferred, which creates lifetime issues
|
||||||
|
// just stop the timer and let the cleanup routine free everything when the script is done
|
||||||
furi_event_loop_timer_stop(subscription->object);
|
furi_event_loop_timer_stop(subscription->object);
|
||||||
} else {
|
return;
|
||||||
furi_event_loop_unsubscribe(subscription->loop, subscription->object);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
furi_event_loop_unsubscribe(subscription->loop, subscription->object);
|
||||||
|
|
||||||
free(subscription->context->arguments);
|
free(subscription->context->arguments);
|
||||||
free(subscription->context);
|
free(subscription->context);
|
||||||
|
|
||||||
@@ -158,6 +162,7 @@ static void js_event_loop_subscribe(struct mjs* mjs) {
|
|||||||
mjs_set(mjs, subscription_obj, "cancel", ~0, MJS_MK_FN(js_event_loop_subscription_cancel));
|
mjs_set(mjs, subscription_obj, "cancel", ~0, MJS_MK_FN(js_event_loop_subscription_cancel));
|
||||||
|
|
||||||
// create callback context
|
// create callback context
|
||||||
|
context->event_loop = module->loop;
|
||||||
context->object_type = contract->object_type;
|
context->object_type = contract->object_type;
|
||||||
context->arity = mjs_nargs(mjs) - SYSTEM_ARGS + 2;
|
context->arity = mjs_nargs(mjs) - SYSTEM_ARGS + 2;
|
||||||
context->arguments = calloc(context->arity, sizeof(mjs_val_t));
|
context->arguments = calloc(context->arity, sizeof(mjs_val_t));
|
||||||
@@ -333,37 +338,22 @@ static void js_event_loop_queue(struct mjs* mjs) {
|
|||||||
mjs_return(mjs, queue);
|
mjs_return(mjs, queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void js_event_loop_tick(void* param) {
|
|
||||||
JsEventLoopTickContext* context = param;
|
|
||||||
uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny | FuriFlagNoClear, 0);
|
|
||||||
if(flags & FuriFlagError) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(flags & ThreadEventStop) {
|
|
||||||
furi_event_loop_stop(context->loop);
|
|
||||||
mjs_exit(context->mjs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void* js_event_loop_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
static void* js_event_loop_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
||||||
UNUSED(modules);
|
UNUSED(modules);
|
||||||
mjs_val_t event_loop_obj = mjs_mk_object(mjs);
|
mjs_val_t event_loop_obj = mjs_mk_object(mjs);
|
||||||
JsEventLoop* module = malloc(sizeof(JsEventLoop));
|
JsEventLoop* module = malloc(sizeof(JsEventLoop));
|
||||||
JsEventLoopTickContext* tick_ctx = malloc(sizeof(JsEventLoopTickContext));
|
|
||||||
module->loop = furi_event_loop_alloc();
|
module->loop = furi_event_loop_alloc();
|
||||||
tick_ctx->loop = module->loop;
|
|
||||||
tick_ctx->mjs = mjs;
|
|
||||||
module->tick_context = tick_ctx;
|
|
||||||
furi_event_loop_tick_set(module->loop, 10, js_event_loop_tick, tick_ctx);
|
|
||||||
SubscriptionArray_init(module->subscriptions);
|
SubscriptionArray_init(module->subscriptions);
|
||||||
ContractArray_init(module->owned_contracts);
|
ContractArray_init(module->owned_contracts);
|
||||||
|
|
||||||
mjs_set(mjs, event_loop_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, module));
|
JS_ASSIGN_MULTI(mjs, event_loop_obj) {
|
||||||
mjs_set(mjs, event_loop_obj, "subscribe", ~0, MJS_MK_FN(js_event_loop_subscribe));
|
JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, module));
|
||||||
mjs_set(mjs, event_loop_obj, "run", ~0, MJS_MK_FN(js_event_loop_run));
|
JS_FIELD("subscribe", MJS_MK_FN(js_event_loop_subscribe));
|
||||||
mjs_set(mjs, event_loop_obj, "stop", ~0, MJS_MK_FN(js_event_loop_stop));
|
JS_FIELD("run", MJS_MK_FN(js_event_loop_run));
|
||||||
mjs_set(mjs, event_loop_obj, "timer", ~0, MJS_MK_FN(js_event_loop_timer));
|
JS_FIELD("stop", MJS_MK_FN(js_event_loop_stop));
|
||||||
mjs_set(mjs, event_loop_obj, "queue", ~0, MJS_MK_FN(js_event_loop_queue));
|
JS_FIELD("timer", MJS_MK_FN(js_event_loop_timer));
|
||||||
|
JS_FIELD("queue", MJS_MK_FN(js_event_loop_queue));
|
||||||
|
}
|
||||||
|
|
||||||
*object = event_loop_obj;
|
*object = event_loop_obj;
|
||||||
return module;
|
return module;
|
||||||
@@ -418,7 +408,6 @@ static void js_event_loop_destroy(void* inst) {
|
|||||||
ContractArray_clear(module->owned_contracts);
|
ContractArray_clear(module->owned_contracts);
|
||||||
|
|
||||||
furi_event_loop_free(module->loop);
|
furi_event_loop_free(module->loop);
|
||||||
free(module->tick_context);
|
|
||||||
free(module);
|
free(module);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "../js_modules.h" // IWYU pragma: keep
|
#include "../js_modules.h" // IWYU pragma: keep
|
||||||
#include "./js_event_loop/js_event_loop.h"
|
#include "./js_event_loop/js_event_loop.h"
|
||||||
#include <furi_hal_gpio.h>
|
#include <furi_hal_gpio.h>
|
||||||
|
#include <furi_hal_pwm.h>
|
||||||
#include <furi_hal_resources.h>
|
#include <furi_hal_resources.h>
|
||||||
#include <expansion/expansion.h>
|
#include <expansion/expansion.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
@@ -17,6 +18,7 @@ typedef struct {
|
|||||||
FuriSemaphore* interrupt_semaphore;
|
FuriSemaphore* interrupt_semaphore;
|
||||||
JsEventLoopContract* interrupt_contract;
|
JsEventLoopContract* interrupt_contract;
|
||||||
FuriHalAdcChannel adc_channel;
|
FuriHalAdcChannel adc_channel;
|
||||||
|
FuriHalPwmOutputId pwm_output;
|
||||||
FuriHalAdcHandle* adc_handle;
|
FuriHalAdcHandle* adc_handle;
|
||||||
} JsGpioPinInst;
|
} JsGpioPinInst;
|
||||||
|
|
||||||
@@ -231,6 +233,88 @@ static void js_gpio_read_analog(struct mjs* mjs) {
|
|||||||
mjs_return(mjs, mjs_mk_number(mjs, (double)millivolts));
|
mjs_return(mjs, mjs_mk_number(mjs, (double)millivolts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Determines whether this pin supports PWM
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* let gpio = require("gpio");
|
||||||
|
* assert_eq(true, gpio.get("pa4").isPwmSupported());
|
||||||
|
* assert_eq(false, gpio.get("pa5").isPwmSupported());
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
static void js_gpio_is_pwm_supported(struct mjs* mjs) {
|
||||||
|
JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs);
|
||||||
|
mjs_return(mjs, mjs_mk_boolean(mjs, manager_data->pwm_output != FuriHalPwmOutputIdNone));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets PWM parameters and starts the PWM
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* let gpio = require("gpio");
|
||||||
|
* let pa4 = gpio.get("pa4");
|
||||||
|
* pa4.pwmWrite(10000, 50);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
static void js_gpio_pwm_write(struct mjs* mjs) {
|
||||||
|
JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs);
|
||||||
|
int32_t frequency, duty;
|
||||||
|
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&frequency), JS_ARG_INT32(&duty));
|
||||||
|
if(manager_data->pwm_output == FuriHalPwmOutputIdNone) {
|
||||||
|
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(furi_hal_pwm_is_running(manager_data->pwm_output)) {
|
||||||
|
furi_hal_pwm_set_params(manager_data->pwm_output, frequency, duty);
|
||||||
|
} else {
|
||||||
|
furi_hal_pwm_start(manager_data->pwm_output, frequency, duty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Determines whether PWM is running
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* let gpio = require("gpio");
|
||||||
|
* assert_eq(false, gpio.get("pa4").isPwmRunning());
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
static void js_gpio_is_pwm_running(struct mjs* mjs) {
|
||||||
|
JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs);
|
||||||
|
if(manager_data->pwm_output == FuriHalPwmOutputIdNone) {
|
||||||
|
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin");
|
||||||
|
}
|
||||||
|
|
||||||
|
mjs_return(mjs, mjs_mk_boolean(mjs, furi_hal_pwm_is_running(manager_data->pwm_output)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stops PWM
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* let gpio = require("gpio");
|
||||||
|
* let pa4 = gpio.get("pa4");
|
||||||
|
* pa4.pwmWrite(10000, 50);
|
||||||
|
* pa4.pwmStop();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
static void js_gpio_pwm_stop(struct mjs* mjs) {
|
||||||
|
JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs);
|
||||||
|
if(manager_data->pwm_output != FuriHalPwmOutputIdNone) {
|
||||||
|
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin");
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_hal_pwm_stop(manager_data->pwm_output);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns an object that manages a specified pin.
|
* @brief Returns an object that manages a specified pin.
|
||||||
*
|
*
|
||||||
@@ -269,12 +353,19 @@ static void js_gpio_get(struct mjs* mjs) {
|
|||||||
manager_data->interrupt_semaphore = furi_semaphore_alloc(UINT32_MAX, 0);
|
manager_data->interrupt_semaphore = furi_semaphore_alloc(UINT32_MAX, 0);
|
||||||
manager_data->adc_handle = module->adc_handle;
|
manager_data->adc_handle = module->adc_handle;
|
||||||
manager_data->adc_channel = pin_record->channel;
|
manager_data->adc_channel = pin_record->channel;
|
||||||
mjs_set(mjs, manager, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, manager_data));
|
manager_data->pwm_output = pin_record->pwm_output;
|
||||||
mjs_set(mjs, manager, "init", ~0, MJS_MK_FN(js_gpio_init));
|
JS_ASSIGN_MULTI(mjs, manager) {
|
||||||
mjs_set(mjs, manager, "write", ~0, MJS_MK_FN(js_gpio_write));
|
JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, manager_data));
|
||||||
mjs_set(mjs, manager, "read", ~0, MJS_MK_FN(js_gpio_read));
|
JS_FIELD("init", MJS_MK_FN(js_gpio_init));
|
||||||
mjs_set(mjs, manager, "readAnalog", ~0, MJS_MK_FN(js_gpio_read_analog));
|
JS_FIELD("write", MJS_MK_FN(js_gpio_write));
|
||||||
mjs_set(mjs, manager, "interrupt", ~0, MJS_MK_FN(js_gpio_interrupt));
|
JS_FIELD("read", MJS_MK_FN(js_gpio_read));
|
||||||
|
JS_FIELD("readAnalog", MJS_MK_FN(js_gpio_read_analog));
|
||||||
|
JS_FIELD("interrupt", MJS_MK_FN(js_gpio_interrupt));
|
||||||
|
JS_FIELD("isPwmSupported", MJS_MK_FN(js_gpio_is_pwm_supported));
|
||||||
|
JS_FIELD("pwmWrite", MJS_MK_FN(js_gpio_pwm_write));
|
||||||
|
JS_FIELD("isPwmRunning", MJS_MK_FN(js_gpio_is_pwm_running));
|
||||||
|
JS_FIELD("pwmStop", MJS_MK_FN(js_gpio_pwm_stop));
|
||||||
|
}
|
||||||
mjs_return(mjs, manager);
|
mjs_return(mjs, manager);
|
||||||
|
|
||||||
// remember pin
|
// remember pin
|
||||||
|
|||||||
61
applications/system/js_app/modules/js_gui/icon.c
Normal file
61
applications/system/js_app/modules/js_gui/icon.c
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#include "../../js_modules.h"
|
||||||
|
#include <assets_icons.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char* name;
|
||||||
|
const Icon* data;
|
||||||
|
} IconDefinition;
|
||||||
|
|
||||||
|
#define ICON_DEF(icon) \
|
||||||
|
(IconDefinition) { \
|
||||||
|
.name = #icon, .data = &I_##icon \
|
||||||
|
}
|
||||||
|
|
||||||
|
static const IconDefinition builtin_icons[] = {
|
||||||
|
ICON_DEF(DolphinWait_59x54),
|
||||||
|
ICON_DEF(js_script_10px),
|
||||||
|
};
|
||||||
|
|
||||||
|
static void js_gui_icon_get_builtin(struct mjs* mjs) {
|
||||||
|
const char* icon_name;
|
||||||
|
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&icon_name));
|
||||||
|
|
||||||
|
for(size_t i = 0; i < COUNT_OF(builtin_icons); i++) {
|
||||||
|
if(strcmp(icon_name, builtin_icons[i].name) == 0) {
|
||||||
|
mjs_return(mjs, mjs_mk_foreign(mjs, (void*)builtin_icons[i].data));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "no such built-in icon");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* js_gui_icon_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
||||||
|
UNUSED(modules);
|
||||||
|
*object = mjs_mk_object(mjs);
|
||||||
|
JS_ASSIGN_MULTI(mjs, *object) {
|
||||||
|
JS_FIELD("getBuiltin", MJS_MK_FN(js_gui_icon_get_builtin));
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void js_gui_icon_destroy(void* inst) {
|
||||||
|
UNUSED(inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const JsModuleDescriptor js_gui_icon_desc = {
|
||||||
|
"gui__icon",
|
||||||
|
js_gui_icon_create,
|
||||||
|
js_gui_icon_destroy,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const FlipperAppPluginDescriptor plugin_descriptor = {
|
||||||
|
.appid = PLUGIN_APP_ID,
|
||||||
|
.ep_api_version = PLUGIN_API_VERSION,
|
||||||
|
.entry_point = &js_gui_icon_desc,
|
||||||
|
};
|
||||||
|
|
||||||
|
const FlipperAppPluginDescriptor* js_gui_icon_ep(void) {
|
||||||
|
return &plugin_descriptor;
|
||||||
|
}
|
||||||
@@ -247,6 +247,22 @@ static bool
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the list of children. Not available from JS.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
js_gui_view_internal_set_children(struct mjs* mjs, mjs_val_t children, JsGuiViewData* data) {
|
||||||
|
data->descriptor->reset_children(data->specific_view, data->custom_data);
|
||||||
|
|
||||||
|
for(size_t i = 0; i < mjs_array_length(mjs, children); i++) {
|
||||||
|
mjs_val_t child = mjs_array_get(mjs, children, i);
|
||||||
|
if(!data->descriptor->add_child(mjs, data->specific_view, data->custom_data, child))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief `View.set`
|
* @brief `View.set`
|
||||||
*/
|
*/
|
||||||
@@ -260,6 +276,46 @@ static void js_gui_view_set(struct mjs* mjs) {
|
|||||||
mjs_return(mjs, MJS_UNDEFINED);
|
mjs_return(mjs, MJS_UNDEFINED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief `View.addChild`
|
||||||
|
*/
|
||||||
|
static void js_gui_view_add_child(struct mjs* mjs) {
|
||||||
|
JsGuiViewData* data = JS_GET_CONTEXT(mjs);
|
||||||
|
if(!data->descriptor->add_child || !data->descriptor->reset_children)
|
||||||
|
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
|
||||||
|
|
||||||
|
mjs_val_t child;
|
||||||
|
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&child));
|
||||||
|
bool success = data->descriptor->add_child(mjs, data->specific_view, data->custom_data, child);
|
||||||
|
UNUSED(success);
|
||||||
|
mjs_return(mjs, MJS_UNDEFINED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief `View.resetChildren`
|
||||||
|
*/
|
||||||
|
static void js_gui_view_reset_children(struct mjs* mjs) {
|
||||||
|
JsGuiViewData* data = JS_GET_CONTEXT(mjs);
|
||||||
|
if(!data->descriptor->add_child || !data->descriptor->reset_children)
|
||||||
|
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
|
||||||
|
|
||||||
|
data->descriptor->reset_children(data->specific_view, data->custom_data);
|
||||||
|
mjs_return(mjs, MJS_UNDEFINED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief `View.setChildren`
|
||||||
|
*/
|
||||||
|
static void js_gui_view_set_children(struct mjs* mjs) {
|
||||||
|
JsGuiViewData* data = JS_GET_CONTEXT(mjs);
|
||||||
|
if(!data->descriptor->add_child || !data->descriptor->reset_children)
|
||||||
|
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
|
||||||
|
|
||||||
|
mjs_val_t children;
|
||||||
|
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&children));
|
||||||
|
js_gui_view_internal_set_children(mjs, children, data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief `View` destructor
|
* @brief `View` destructor
|
||||||
*/
|
*/
|
||||||
@@ -283,7 +339,12 @@ static mjs_val_t js_gui_make_view(struct mjs* mjs, const JsViewDescriptor* descr
|
|||||||
|
|
||||||
// generic view API
|
// generic view API
|
||||||
mjs_val_t view_obj = mjs_mk_object(mjs);
|
mjs_val_t view_obj = mjs_mk_object(mjs);
|
||||||
mjs_set(mjs, view_obj, "set", ~0, MJS_MK_FN(js_gui_view_set));
|
JS_ASSIGN_MULTI(mjs, view_obj) {
|
||||||
|
JS_FIELD("set", MJS_MK_FN(js_gui_view_set));
|
||||||
|
JS_FIELD("addChild", MJS_MK_FN(js_gui_view_add_child));
|
||||||
|
JS_FIELD("resetChildren", MJS_MK_FN(js_gui_view_reset_children));
|
||||||
|
JS_FIELD("setChildren", MJS_MK_FN(js_gui_view_set_children));
|
||||||
|
}
|
||||||
|
|
||||||
// object data
|
// object data
|
||||||
JsGuiViewData* data = malloc(sizeof(JsGuiViewData));
|
JsGuiViewData* data = malloc(sizeof(JsGuiViewData));
|
||||||
@@ -314,7 +375,7 @@ static void js_gui_vf_make(struct mjs* mjs) {
|
|||||||
*/
|
*/
|
||||||
static void js_gui_vf_make_with(struct mjs* mjs) {
|
static void js_gui_vf_make_with(struct mjs* mjs) {
|
||||||
mjs_val_t props;
|
mjs_val_t props;
|
||||||
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&props));
|
JS_FETCH_ARGS_OR_RETURN(mjs, JS_AT_LEAST, JS_ARG_OBJ(&props));
|
||||||
const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs);
|
const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs);
|
||||||
|
|
||||||
// make the object like normal
|
// make the object like normal
|
||||||
@@ -334,6 +395,18 @@ static void js_gui_vf_make_with(struct mjs* mjs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// assign children
|
||||||
|
if(mjs_nargs(mjs) >= 2) {
|
||||||
|
if(!data->descriptor->add_child || !data->descriptor->reset_children)
|
||||||
|
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
|
||||||
|
|
||||||
|
mjs_val_t children = mjs_arg(mjs, 1);
|
||||||
|
if(!mjs_is_array(children))
|
||||||
|
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "argument 1: expected array");
|
||||||
|
|
||||||
|
if(!js_gui_view_internal_set_children(mjs, children, data)) return;
|
||||||
|
}
|
||||||
|
|
||||||
mjs_return(mjs, view_obj);
|
mjs_return(mjs, view_obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ typedef void (*JsViewFree)(void* specific_view);
|
|||||||
typedef void* (*JsViewCustomMake)(struct mjs* mjs, void* specific_view, mjs_val_t view_obj);
|
typedef void* (*JsViewCustomMake)(struct mjs* mjs, void* specific_view, mjs_val_t view_obj);
|
||||||
/** @brief Context destruction for glue code */
|
/** @brief Context destruction for glue code */
|
||||||
typedef void (*JsViewCustomDestroy)(void* specific_view, void* custom_state, FuriEventLoop* loop);
|
typedef void (*JsViewCustomDestroy)(void* specific_view, void* custom_state, FuriEventLoop* loop);
|
||||||
|
/** @brief `addChild` callback for glue code */
|
||||||
|
typedef bool (
|
||||||
|
*JsViewAddChild)(struct mjs* mjs, void* specific_view, void* custom_state, mjs_val_t child_obj);
|
||||||
|
/** @brief `resetChildren` callback for glue code */
|
||||||
|
typedef void (*JsViewResetChildren)(void* specific_view, void* custom_state);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Descriptor for a JS view
|
* @brief Descriptor for a JS view
|
||||||
@@ -66,15 +71,22 @@ typedef struct {
|
|||||||
JsViewAlloc alloc;
|
JsViewAlloc alloc;
|
||||||
JsViewGetView get_view;
|
JsViewGetView get_view;
|
||||||
JsViewFree free;
|
JsViewFree free;
|
||||||
|
|
||||||
JsViewCustomMake custom_make; // <! May be NULL
|
JsViewCustomMake custom_make; // <! May be NULL
|
||||||
JsViewCustomDestroy custom_destroy; // <! May be NULL
|
JsViewCustomDestroy custom_destroy; // <! May be NULL
|
||||||
|
|
||||||
|
JsViewAddChild add_child; // <! May be NULL
|
||||||
|
JsViewResetChildren reset_children; // <! May be NULL
|
||||||
|
|
||||||
size_t prop_cnt; //<! Number of properties visible from JS
|
size_t prop_cnt; //<! Number of properties visible from JS
|
||||||
JsViewPropDescriptor props[]; // <! Descriptors of properties visible from JS
|
JsViewPropDescriptor props[]; // <! Descriptors of properties visible from JS
|
||||||
} JsViewDescriptor;
|
} JsViewDescriptor;
|
||||||
|
|
||||||
// Callback ordering:
|
// Callback ordering:
|
||||||
// alloc -> get_view -> [custom_make (if set)] -> props[i].assign -> [custom_destroy (if_set)] -> free
|
// +-> add_child -+
|
||||||
// \_______________ creation ________________/ \___ usage ___/ \_________ destruction _________/
|
// +-> reset_children -+
|
||||||
|
// alloc -> get_view -> custom_make -+-> props[i].assign -+> custom_destroy -> free
|
||||||
|
// \__________ creation __________/ \____ use ____/ \___ destruction ____/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Creates a JS `ViewFactory` object
|
* @brief Creates a JS `ViewFactory` object
|
||||||
|
|||||||
281
applications/system/js_app/modules/js_gui/widget.c
Normal file
281
applications/system/js_app/modules/js_gui/widget.c
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
#include "../../js_modules.h" // IWYU pragma: keep
|
||||||
|
#include "js_gui.h"
|
||||||
|
#include "../js_event_loop/js_event_loop.h"
|
||||||
|
#include <gui/modules/widget.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FuriMessageQueue* queue;
|
||||||
|
JsEventLoopContract contract;
|
||||||
|
} JsWidgetCtx;
|
||||||
|
|
||||||
|
#define QUEUE_LEN 2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parses position (X and Y) from an element declaration object
|
||||||
|
*/
|
||||||
|
static bool element_get_position(struct mjs* mjs, mjs_val_t element, int32_t* x, int32_t* y) {
|
||||||
|
mjs_val_t x_in = mjs_get(mjs, element, "x", ~0);
|
||||||
|
mjs_val_t y_in = mjs_get(mjs, element, "y", ~0);
|
||||||
|
if(!mjs_is_number(x_in) || !mjs_is_number(y_in)) return false;
|
||||||
|
*x = mjs_get_int32(mjs, x_in);
|
||||||
|
*y = mjs_get_int32(mjs, y_in);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parses size (W and h) from an element declaration object
|
||||||
|
*/
|
||||||
|
static bool element_get_size(struct mjs* mjs, mjs_val_t element, int32_t* w, int32_t* h) {
|
||||||
|
mjs_val_t w_in = mjs_get(mjs, element, "w", ~0);
|
||||||
|
mjs_val_t h_in = mjs_get(mjs, element, "h", ~0);
|
||||||
|
if(!mjs_is_number(w_in) || !mjs_is_number(h_in)) return false;
|
||||||
|
*w = mjs_get_int32(mjs, w_in);
|
||||||
|
*h = mjs_get_int32(mjs, h_in);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parses alignment (V and H) from an element declaration object
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
element_get_alignment(struct mjs* mjs, mjs_val_t element, Align* align_v, Align* align_h) {
|
||||||
|
mjs_val_t align_in = mjs_get(mjs, element, "align", ~0);
|
||||||
|
const char* align = mjs_get_string(mjs, &align_in, NULL);
|
||||||
|
if(!align) return false;
|
||||||
|
if(strlen(align) != 2) return false;
|
||||||
|
|
||||||
|
if(align[0] == 't') {
|
||||||
|
*align_v = AlignTop;
|
||||||
|
} else if(align[0] == 'c') {
|
||||||
|
*align_v = AlignCenter;
|
||||||
|
} else if(align[0] == 'b') {
|
||||||
|
*align_v = AlignBottom;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(align[1] == 'l') {
|
||||||
|
*align_h = AlignLeft;
|
||||||
|
} else if(align[1] == 'm') { // m = middle
|
||||||
|
*align_h = AlignCenter;
|
||||||
|
} else if(align[1] == 'r') {
|
||||||
|
*align_h = AlignRight;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parses font from an element declaration object
|
||||||
|
*/
|
||||||
|
static bool element_get_font(struct mjs* mjs, mjs_val_t element, Font* font) {
|
||||||
|
mjs_val_t font_in = mjs_get(mjs, element, "font", ~0);
|
||||||
|
const char* font_str = mjs_get_string(mjs, &font_in, NULL);
|
||||||
|
if(!font_str) return false;
|
||||||
|
|
||||||
|
if(strcmp(font_str, "primary") == 0) {
|
||||||
|
*font = FontPrimary;
|
||||||
|
} else if(strcmp(font_str, "secondary") == 0) {
|
||||||
|
*font = FontSecondary;
|
||||||
|
} else if(strcmp(font_str, "keyboard") == 0) {
|
||||||
|
*font = FontKeyboard;
|
||||||
|
} else if(strcmp(font_str, "big_numbers") == 0) {
|
||||||
|
*font = FontBigNumbers;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parses text from an element declaration object
|
||||||
|
*/
|
||||||
|
static bool element_get_text(struct mjs* mjs, mjs_val_t element, mjs_val_t* text) {
|
||||||
|
*text = mjs_get(mjs, element, "text", ~0);
|
||||||
|
return mjs_is_string(*text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Widget button element callback
|
||||||
|
*/
|
||||||
|
static void js_widget_button_callback(GuiButtonType result, InputType type, JsWidgetCtx* context) {
|
||||||
|
UNUSED(type);
|
||||||
|
furi_check(furi_message_queue_put(context->queue, &result, 0) == FuriStatusOk);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DESTRUCTURE_OR_RETURN(mjs, child_obj, part, ...) \
|
||||||
|
if(!element_get_##part(mjs, child_obj, __VA_ARGS__)) \
|
||||||
|
JS_ERROR_AND_RETURN_VAL(mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element " #part);
|
||||||
|
|
||||||
|
static bool js_widget_add_child(
|
||||||
|
struct mjs* mjs,
|
||||||
|
Widget* widget,
|
||||||
|
JsWidgetCtx* context,
|
||||||
|
mjs_val_t child_obj) {
|
||||||
|
UNUSED(context);
|
||||||
|
if(!mjs_is_object(child_obj))
|
||||||
|
JS_ERROR_AND_RETURN_VAL(mjs, MJS_BAD_ARGS_ERROR, false, "child must be an object");
|
||||||
|
|
||||||
|
mjs_val_t element_type_term = mjs_get(mjs, child_obj, "element", ~0);
|
||||||
|
const char* element_type = mjs_get_string(mjs, &element_type_term, NULL);
|
||||||
|
if(!element_type)
|
||||||
|
JS_ERROR_AND_RETURN_VAL(
|
||||||
|
mjs, MJS_BAD_ARGS_ERROR, false, "child object must have `element` property");
|
||||||
|
|
||||||
|
if((strcmp(element_type, "string") == 0) || (strcmp(element_type, "string_multiline") == 0)) {
|
||||||
|
int32_t x, y;
|
||||||
|
Align align_v, align_h;
|
||||||
|
Font font;
|
||||||
|
mjs_val_t text;
|
||||||
|
DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y);
|
||||||
|
DESTRUCTURE_OR_RETURN(mjs, child_obj, alignment, &align_v, &align_h);
|
||||||
|
DESTRUCTURE_OR_RETURN(mjs, child_obj, font, &font);
|
||||||
|
DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text);
|
||||||
|
if(strcmp(element_type, "string") == 0) {
|
||||||
|
widget_add_string_element(
|
||||||
|
widget, x, y, align_h, align_v, font, mjs_get_string(mjs, &text, NULL));
|
||||||
|
} else {
|
||||||
|
widget_add_string_multiline_element(
|
||||||
|
widget, x, y, align_h, align_v, font, mjs_get_string(mjs, &text, NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if(strcmp(element_type, "text_box") == 0) {
|
||||||
|
int32_t x, y, w, h;
|
||||||
|
Align align_v, align_h;
|
||||||
|
Font font;
|
||||||
|
mjs_val_t text;
|
||||||
|
DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y);
|
||||||
|
DESTRUCTURE_OR_RETURN(mjs, child_obj, size, &w, &h);
|
||||||
|
DESTRUCTURE_OR_RETURN(mjs, child_obj, alignment, &align_v, &align_h);
|
||||||
|
DESTRUCTURE_OR_RETURN(mjs, child_obj, font, &font);
|
||||||
|
DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text);
|
||||||
|
mjs_val_t strip_to_dots_in = mjs_get(mjs, child_obj, "stripToDots", ~0);
|
||||||
|
if(!mjs_is_boolean(strip_to_dots_in))
|
||||||
|
JS_ERROR_AND_RETURN_VAL(
|
||||||
|
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element stripToDots");
|
||||||
|
bool strip_to_dots = mjs_get_bool(mjs, strip_to_dots_in);
|
||||||
|
widget_add_text_box_element(
|
||||||
|
widget, x, y, w, h, align_h, align_v, mjs_get_string(mjs, &text, NULL), strip_to_dots);
|
||||||
|
|
||||||
|
} else if(strcmp(element_type, "text_scroll") == 0) {
|
||||||
|
int32_t x, y, w, h;
|
||||||
|
mjs_val_t text;
|
||||||
|
DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y);
|
||||||
|
DESTRUCTURE_OR_RETURN(mjs, child_obj, size, &w, &h);
|
||||||
|
DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text);
|
||||||
|
widget_add_text_scroll_element(widget, x, y, w, h, mjs_get_string(mjs, &text, NULL));
|
||||||
|
|
||||||
|
} else if(strcmp(element_type, "button") == 0) {
|
||||||
|
mjs_val_t btn_in = mjs_get(mjs, child_obj, "button", ~0);
|
||||||
|
const char* btn_name = mjs_get_string(mjs, &btn_in, NULL);
|
||||||
|
if(!btn_name)
|
||||||
|
JS_ERROR_AND_RETURN_VAL(
|
||||||
|
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element button");
|
||||||
|
GuiButtonType btn_type;
|
||||||
|
if(strcmp(btn_name, "left") == 0) {
|
||||||
|
btn_type = GuiButtonTypeLeft;
|
||||||
|
} else if(strcmp(btn_name, "center") == 0) {
|
||||||
|
btn_type = GuiButtonTypeCenter;
|
||||||
|
} else if(strcmp(btn_name, "right") == 0) {
|
||||||
|
btn_type = GuiButtonTypeRight;
|
||||||
|
} else {
|
||||||
|
JS_ERROR_AND_RETURN_VAL(mjs, MJS_BAD_ARGS_ERROR, false, "incorrect button type");
|
||||||
|
}
|
||||||
|
mjs_val_t text;
|
||||||
|
DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text);
|
||||||
|
widget_add_button_element(
|
||||||
|
widget,
|
||||||
|
btn_type,
|
||||||
|
mjs_get_string(mjs, &text, NULL),
|
||||||
|
(ButtonCallback)js_widget_button_callback,
|
||||||
|
context);
|
||||||
|
|
||||||
|
} else if(strcmp(element_type, "icon") == 0) {
|
||||||
|
int32_t x, y;
|
||||||
|
DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y);
|
||||||
|
mjs_val_t icon_data_in = mjs_get(mjs, child_obj, "iconData", ~0);
|
||||||
|
if(!mjs_is_foreign(icon_data_in))
|
||||||
|
JS_ERROR_AND_RETURN_VAL(
|
||||||
|
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element iconData");
|
||||||
|
const Icon* icon = mjs_get_ptr(mjs, icon_data_in);
|
||||||
|
widget_add_icon_element(widget, x, y, icon);
|
||||||
|
|
||||||
|
} else if(strcmp(element_type, "frame") == 0) {
|
||||||
|
int32_t x, y, w, h;
|
||||||
|
DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y);
|
||||||
|
DESTRUCTURE_OR_RETURN(mjs, child_obj, size, &w, &h);
|
||||||
|
mjs_val_t radius_in = mjs_get(mjs, child_obj, "radius", ~0);
|
||||||
|
if(!mjs_is_number(radius_in))
|
||||||
|
JS_ERROR_AND_RETURN_VAL(
|
||||||
|
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element radius");
|
||||||
|
int32_t radius = mjs_get_int32(mjs, radius_in);
|
||||||
|
widget_add_frame_element(widget, x, y, w, h, radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void js_widget_reset_children(Widget* widget, void* state) {
|
||||||
|
UNUSED(state);
|
||||||
|
widget_reset(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
static mjs_val_t js_widget_button_event_transformer(
|
||||||
|
struct mjs* mjs,
|
||||||
|
FuriMessageQueue* queue,
|
||||||
|
JsWidgetCtx* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
GuiButtonType btn_type;
|
||||||
|
furi_check(furi_message_queue_get(queue, &btn_type, 0) == FuriStatusOk);
|
||||||
|
const char* btn_name;
|
||||||
|
if(btn_type == GuiButtonTypeLeft) {
|
||||||
|
btn_name = "left";
|
||||||
|
} else if(btn_type == GuiButtonTypeCenter) {
|
||||||
|
btn_name = "center";
|
||||||
|
} else if(btn_type == GuiButtonTypeRight) {
|
||||||
|
btn_name = "right";
|
||||||
|
} else {
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
return mjs_mk_string(mjs, btn_name, ~0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* js_widget_custom_make(struct mjs* mjs, Widget* widget, mjs_val_t view_obj) {
|
||||||
|
UNUSED(widget);
|
||||||
|
JsWidgetCtx* context = malloc(sizeof(JsWidgetCtx));
|
||||||
|
context->queue = furi_message_queue_alloc(QUEUE_LEN, sizeof(GuiButtonType));
|
||||||
|
context->contract = (JsEventLoopContract){
|
||||||
|
.magic = JsForeignMagic_JsEventLoopContract,
|
||||||
|
.object_type = JsEventLoopObjectTypeQueue,
|
||||||
|
.object = context->queue,
|
||||||
|
.non_timer =
|
||||||
|
{
|
||||||
|
.event = FuriEventLoopEventIn,
|
||||||
|
.transformer = (JsEventLoopTransformer)js_widget_button_event_transformer,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mjs_set(mjs, view_obj, "button", ~0, mjs_mk_foreign(mjs, &context->contract));
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void js_widget_custom_destroy(Widget* widget, JsWidgetCtx* context, FuriEventLoop* loop) {
|
||||||
|
UNUSED(widget);
|
||||||
|
furi_event_loop_maybe_unsubscribe(loop, context->queue);
|
||||||
|
furi_message_queue_free(context->queue);
|
||||||
|
free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const JsViewDescriptor view_descriptor = {
|
||||||
|
.alloc = (JsViewAlloc)widget_alloc,
|
||||||
|
.free = (JsViewFree)widget_free,
|
||||||
|
.get_view = (JsViewGetView)widget_get_view,
|
||||||
|
.custom_make = (JsViewCustomMake)js_widget_custom_make,
|
||||||
|
.custom_destroy = (JsViewCustomDestroy)js_widget_custom_destroy,
|
||||||
|
.add_child = (JsViewAddChild)js_widget_add_child,
|
||||||
|
.reset_children = (JsViewResetChildren)js_widget_reset_children,
|
||||||
|
.prop_cnt = 0,
|
||||||
|
.props = {},
|
||||||
|
};
|
||||||
|
JS_GUI_VIEW_DEF(widget, &view_descriptor);
|
||||||
@@ -1,922 +0,0 @@
|
|||||||
#include <assets_icons.h>
|
|
||||||
#include <gui/view_holder.h>
|
|
||||||
#include <m-array.h>
|
|
||||||
#include <m-list.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "../js_modules.h"
|
|
||||||
|
|
||||||
typedef struct WidgetComponent WidgetComponent;
|
|
||||||
ARRAY_DEF(ComponentArray, WidgetComponent*, M_PTR_OPLIST);
|
|
||||||
|
|
||||||
typedef struct XbmImage XbmImage;
|
|
||||||
LIST_DEF(XbmImageList, XbmImage*, M_POD_OPLIST);
|
|
||||||
|
|
||||||
struct WidgetComponent {
|
|
||||||
void (*draw)(Canvas* canvas, void* model);
|
|
||||||
void (*free)(WidgetComponent* component);
|
|
||||||
void* model;
|
|
||||||
uint32_t id;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct XbmImage {
|
|
||||||
uint32_t width;
|
|
||||||
uint32_t height;
|
|
||||||
uint8_t data[];
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t x;
|
|
||||||
uint8_t y;
|
|
||||||
uint8_t w;
|
|
||||||
uint8_t h;
|
|
||||||
} BoxElement;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t x;
|
|
||||||
uint8_t y;
|
|
||||||
uint8_t r;
|
|
||||||
} CircleElement;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t x;
|
|
||||||
uint8_t y;
|
|
||||||
uint8_t r;
|
|
||||||
} DiscElement;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t x;
|
|
||||||
uint8_t y;
|
|
||||||
} DotElement;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t x;
|
|
||||||
uint8_t y;
|
|
||||||
const Icon* icon;
|
|
||||||
} IconElement;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t x;
|
|
||||||
uint8_t y;
|
|
||||||
uint8_t w;
|
|
||||||
uint8_t h;
|
|
||||||
} FrameElement;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t x;
|
|
||||||
uint8_t y;
|
|
||||||
uint16_t ch;
|
|
||||||
} GlyphElement;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t x1;
|
|
||||||
uint8_t y1;
|
|
||||||
uint8_t x2;
|
|
||||||
uint8_t y2;
|
|
||||||
} LineElement;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t x;
|
|
||||||
uint8_t y;
|
|
||||||
uint8_t w;
|
|
||||||
uint8_t h;
|
|
||||||
uint8_t r;
|
|
||||||
} RboxElement;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t x;
|
|
||||||
uint8_t y;
|
|
||||||
uint8_t w;
|
|
||||||
uint8_t h;
|
|
||||||
uint8_t r;
|
|
||||||
} RframeElement;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t x;
|
|
||||||
uint8_t y;
|
|
||||||
Font font;
|
|
||||||
FuriString* text;
|
|
||||||
} TextElement;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t x;
|
|
||||||
uint8_t y;
|
|
||||||
uint32_t index;
|
|
||||||
View* view;
|
|
||||||
} XbmElement;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
ComponentArray_t component;
|
|
||||||
XbmImageList_t image;
|
|
||||||
uint32_t max_assigned_id;
|
|
||||||
} WidgetModel;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
View* view;
|
|
||||||
ViewHolder* view_holder;
|
|
||||||
bool is_shown;
|
|
||||||
} JsWidgetInst;
|
|
||||||
|
|
||||||
static JsWidgetInst* get_this_ctx(struct mjs* mjs) {
|
|
||||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
|
||||||
JsWidgetInst* widget = mjs_get_ptr(mjs, obj_inst);
|
|
||||||
furi_assert(widget);
|
|
||||||
return widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ret_bad_args(struct mjs* mjs, const char* error) {
|
|
||||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error);
|
|
||||||
mjs_return(mjs, MJS_UNDEFINED);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool check_arg_count(struct mjs* mjs, size_t count) {
|
|
||||||
size_t num_args = mjs_nargs(mjs);
|
|
||||||
if(num_args != count) {
|
|
||||||
ret_bad_args(mjs, "Wrong argument count");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void js_widget_load_image_xbm(struct mjs* mjs) {
|
|
||||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
|
||||||
if(!check_arg_count(mjs, 1)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mjs_val_t path_arg = mjs_arg(mjs, 0);
|
|
||||||
size_t path_len = 0;
|
|
||||||
const char* path = mjs_get_string(mjs, &path_arg, &path_len);
|
|
||||||
if(!path) {
|
|
||||||
ret_bad_args(mjs, "Path must be a string");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
|
||||||
File* file = storage_file_alloc(storage);
|
|
||||||
XbmImage* xbm = NULL;
|
|
||||||
|
|
||||||
do {
|
|
||||||
if(!storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
|
||||||
ret_bad_args(mjs, "Failed to open file");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t size = 0;
|
|
||||||
if(storage_file_read(file, &size, sizeof(size)) != sizeof(size)) {
|
|
||||||
ret_bad_args(mjs, "Failed to get file size");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
xbm = malloc(size);
|
|
||||||
if(storage_file_read(file, xbm, size) != size) {
|
|
||||||
ret_bad_args(mjs, "Failed to load entire file");
|
|
||||||
free(xbm);
|
|
||||||
xbm = NULL;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while(false);
|
|
||||||
|
|
||||||
storage_file_free(file);
|
|
||||||
furi_record_close(RECORD_STORAGE);
|
|
||||||
|
|
||||||
if(xbm == NULL) {
|
|
||||||
mjs_return(mjs, MJS_UNDEFINED);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t count = 0;
|
|
||||||
with_view_model(
|
|
||||||
widget->view,
|
|
||||||
WidgetModel * model,
|
|
||||||
{
|
|
||||||
count = XbmImageList_size(model->image);
|
|
||||||
XbmImageList_push_back(model->image, xbm);
|
|
||||||
},
|
|
||||||
false);
|
|
||||||
|
|
||||||
mjs_return(mjs, mjs_mk_number(mjs, count));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void js_widget_remove(struct mjs* mjs) {
|
|
||||||
bool removed = false;
|
|
||||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
|
||||||
if(!check_arg_count(mjs, 1)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
with_view_model(
|
|
||||||
widget->view,
|
|
||||||
WidgetModel * model,
|
|
||||||
{
|
|
||||||
uint32_t id = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
|
||||||
ComponentArray_it_t it;
|
|
||||||
ComponentArray_it(it, model->component);
|
|
||||||
while(!ComponentArray_end_p(it)) {
|
|
||||||
WidgetComponent* component = *ComponentArray_ref(it);
|
|
||||||
if(component->id == id) {
|
|
||||||
if(component->free) {
|
|
||||||
component->free(component);
|
|
||||||
}
|
|
||||||
ComponentArray_remove(model->component, it);
|
|
||||||
removed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ComponentArray_next(it);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
true);
|
|
||||||
|
|
||||||
mjs_return(mjs, mjs_mk_boolean(mjs, removed));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_box_draw(Canvas* canvas, void* model) {
|
|
||||||
BoxElement* element = model;
|
|
||||||
canvas_draw_box(canvas, element->x, element->y, element->w, element->h);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_box_free(WidgetComponent* component) {
|
|
||||||
BoxElement* element = component->model;
|
|
||||||
free(element);
|
|
||||||
free(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void js_widget_add_box(struct mjs* mjs) {
|
|
||||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
|
||||||
if(!check_arg_count(mjs, 4)) return;
|
|
||||||
|
|
||||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
|
||||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
|
||||||
int32_t w = mjs_get_int32(mjs, mjs_arg(mjs, 2));
|
|
||||||
int32_t h = mjs_get_int32(mjs, mjs_arg(mjs, 3));
|
|
||||||
|
|
||||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
|
||||||
component->draw = widget_box_draw;
|
|
||||||
component->free = widget_box_free;
|
|
||||||
component->model = malloc(sizeof(BoxElement));
|
|
||||||
BoxElement* element = component->model;
|
|
||||||
element->x = x;
|
|
||||||
element->y = y;
|
|
||||||
element->w = w;
|
|
||||||
element->h = h;
|
|
||||||
|
|
||||||
with_view_model(
|
|
||||||
widget->view,
|
|
||||||
WidgetModel * model,
|
|
||||||
{
|
|
||||||
++model->max_assigned_id;
|
|
||||||
component->id = model->max_assigned_id;
|
|
||||||
ComponentArray_push_back(model->component, component);
|
|
||||||
},
|
|
||||||
true);
|
|
||||||
|
|
||||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_circle_draw(Canvas* canvas, void* model) {
|
|
||||||
CircleElement* element = model;
|
|
||||||
canvas_draw_circle(canvas, element->x, element->y, element->r);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_circle_free(WidgetComponent* component) {
|
|
||||||
CircleElement* element = component->model;
|
|
||||||
free(element);
|
|
||||||
free(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void js_widget_add_circle(struct mjs* mjs) {
|
|
||||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
|
||||||
if(!check_arg_count(mjs, 3)) return;
|
|
||||||
|
|
||||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
|
||||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
|
||||||
int32_t r = mjs_get_int32(mjs, mjs_arg(mjs, 2));
|
|
||||||
|
|
||||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
|
||||||
component->draw = widget_circle_draw;
|
|
||||||
component->free = widget_circle_free;
|
|
||||||
component->model = malloc(sizeof(CircleElement));
|
|
||||||
CircleElement* element = component->model;
|
|
||||||
element->x = x;
|
|
||||||
element->y = y;
|
|
||||||
element->r = r;
|
|
||||||
|
|
||||||
with_view_model(
|
|
||||||
widget->view,
|
|
||||||
WidgetModel * model,
|
|
||||||
{
|
|
||||||
++model->max_assigned_id;
|
|
||||||
component->id = model->max_assigned_id;
|
|
||||||
ComponentArray_push_back(model->component, component);
|
|
||||||
},
|
|
||||||
true);
|
|
||||||
|
|
||||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_disc_draw(Canvas* canvas, void* model) {
|
|
||||||
DiscElement* element = model;
|
|
||||||
canvas_draw_disc(canvas, element->x, element->y, element->r);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_disc_free(WidgetComponent* component) {
|
|
||||||
DiscElement* element = component->model;
|
|
||||||
free(element);
|
|
||||||
free(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void js_widget_add_disc(struct mjs* mjs) {
|
|
||||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
|
||||||
if(!check_arg_count(mjs, 3)) return;
|
|
||||||
|
|
||||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
|
||||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
|
||||||
int32_t r = mjs_get_int32(mjs, mjs_arg(mjs, 2));
|
|
||||||
|
|
||||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
|
||||||
component->draw = widget_disc_draw;
|
|
||||||
component->free = widget_disc_free;
|
|
||||||
component->model = malloc(sizeof(DiscElement));
|
|
||||||
DiscElement* element = component->model;
|
|
||||||
element->x = x;
|
|
||||||
element->y = y;
|
|
||||||
element->r = r;
|
|
||||||
|
|
||||||
with_view_model(
|
|
||||||
widget->view,
|
|
||||||
WidgetModel * model,
|
|
||||||
{
|
|
||||||
++model->max_assigned_id;
|
|
||||||
component->id = model->max_assigned_id;
|
|
||||||
ComponentArray_push_back(model->component, component);
|
|
||||||
},
|
|
||||||
true);
|
|
||||||
|
|
||||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_dot_draw(Canvas* canvas, void* model) {
|
|
||||||
DotElement* element = model;
|
|
||||||
canvas_draw_dot(canvas, element->x, element->y);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_dot_free(WidgetComponent* component) {
|
|
||||||
DotElement* element = component->model;
|
|
||||||
free(element);
|
|
||||||
free(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void js_widget_add_dot(struct mjs* mjs) {
|
|
||||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
|
||||||
if(!check_arg_count(mjs, 2)) return;
|
|
||||||
|
|
||||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
|
||||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
|
||||||
|
|
||||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
|
||||||
component->draw = widget_dot_draw;
|
|
||||||
component->free = widget_dot_free;
|
|
||||||
component->model = malloc(sizeof(DotElement));
|
|
||||||
DotElement* element = component->model;
|
|
||||||
element->x = x;
|
|
||||||
element->y = y;
|
|
||||||
|
|
||||||
with_view_model(
|
|
||||||
widget->view,
|
|
||||||
WidgetModel * model,
|
|
||||||
{
|
|
||||||
++model->max_assigned_id;
|
|
||||||
component->id = model->max_assigned_id;
|
|
||||||
ComponentArray_push_back(model->component, component);
|
|
||||||
},
|
|
||||||
true);
|
|
||||||
|
|
||||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_frame_draw(Canvas* canvas, void* model) {
|
|
||||||
FrameElement* element = model;
|
|
||||||
canvas_draw_frame(canvas, element->x, element->y, element->w, element->h);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_frame_free(WidgetComponent* component) {
|
|
||||||
FrameElement* element = component->model;
|
|
||||||
free(element);
|
|
||||||
free(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void js_widget_add_frame(struct mjs* mjs) {
|
|
||||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
|
||||||
if(!check_arg_count(mjs, 4)) return;
|
|
||||||
|
|
||||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
|
||||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
|
||||||
int32_t w = mjs_get_int32(mjs, mjs_arg(mjs, 2));
|
|
||||||
int32_t h = mjs_get_int32(mjs, mjs_arg(mjs, 3));
|
|
||||||
|
|
||||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
|
||||||
component->draw = widget_frame_draw;
|
|
||||||
component->free = widget_frame_free;
|
|
||||||
component->model = malloc(sizeof(FrameElement));
|
|
||||||
FrameElement* element = component->model;
|
|
||||||
element->x = x;
|
|
||||||
element->y = y;
|
|
||||||
element->w = w;
|
|
||||||
element->h = h;
|
|
||||||
|
|
||||||
with_view_model(
|
|
||||||
widget->view,
|
|
||||||
WidgetModel * model,
|
|
||||||
{
|
|
||||||
++model->max_assigned_id;
|
|
||||||
component->id = model->max_assigned_id;
|
|
||||||
ComponentArray_push_back(model->component, component);
|
|
||||||
},
|
|
||||||
true);
|
|
||||||
|
|
||||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_glyph_draw(Canvas* canvas, void* model) {
|
|
||||||
GlyphElement* element = model;
|
|
||||||
canvas_draw_glyph(canvas, element->x, element->y, element->ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_glyph_free(WidgetComponent* component) {
|
|
||||||
GlyphElement* element = component->model;
|
|
||||||
free(element);
|
|
||||||
free(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void js_widget_add_glyph(struct mjs* mjs) {
|
|
||||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
|
||||||
if(!check_arg_count(mjs, 3)) return;
|
|
||||||
|
|
||||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
|
||||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
|
||||||
int32_t ch = mjs_get_int32(mjs, mjs_arg(mjs, 2));
|
|
||||||
|
|
||||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
|
||||||
component->draw = widget_glyph_draw;
|
|
||||||
component->free = widget_glyph_free;
|
|
||||||
component->model = malloc(sizeof(GlyphElement));
|
|
||||||
GlyphElement* element = component->model;
|
|
||||||
element->x = x;
|
|
||||||
element->y = y;
|
|
||||||
element->ch = ch;
|
|
||||||
|
|
||||||
with_view_model(
|
|
||||||
widget->view,
|
|
||||||
WidgetModel * model,
|
|
||||||
{
|
|
||||||
++model->max_assigned_id;
|
|
||||||
component->id = model->max_assigned_id;
|
|
||||||
ComponentArray_push_back(model->component, component);
|
|
||||||
},
|
|
||||||
true);
|
|
||||||
|
|
||||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_line_draw(Canvas* canvas, void* model) {
|
|
||||||
LineElement* element = model;
|
|
||||||
canvas_draw_line(canvas, element->x1, element->y1, element->x2, element->y2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_line_free(WidgetComponent* component) {
|
|
||||||
LineElement* element = component->model;
|
|
||||||
free(element);
|
|
||||||
free(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void js_widget_add_line(struct mjs* mjs) {
|
|
||||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
|
||||||
if(!check_arg_count(mjs, 4)) return;
|
|
||||||
|
|
||||||
int32_t x1 = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
|
||||||
int32_t y1 = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
|
||||||
int32_t x2 = mjs_get_int32(mjs, mjs_arg(mjs, 2));
|
|
||||||
int32_t y2 = mjs_get_int32(mjs, mjs_arg(mjs, 3));
|
|
||||||
|
|
||||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
|
||||||
component->draw = widget_line_draw;
|
|
||||||
component->free = widget_line_free;
|
|
||||||
component->model = malloc(sizeof(LineElement));
|
|
||||||
LineElement* element = component->model;
|
|
||||||
element->x1 = x1;
|
|
||||||
element->y1 = y1;
|
|
||||||
element->x2 = x2;
|
|
||||||
element->y2 = y2;
|
|
||||||
|
|
||||||
with_view_model(
|
|
||||||
widget->view,
|
|
||||||
WidgetModel * model,
|
|
||||||
{
|
|
||||||
++model->max_assigned_id;
|
|
||||||
component->id = model->max_assigned_id;
|
|
||||||
ComponentArray_push_back(model->component, component);
|
|
||||||
},
|
|
||||||
true);
|
|
||||||
|
|
||||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_rbox_draw(Canvas* canvas, void* model) {
|
|
||||||
RboxElement* element = model;
|
|
||||||
canvas_draw_rbox(canvas, element->x, element->y, element->w, element->h, element->r);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_rbox_free(WidgetComponent* component) {
|
|
||||||
BoxElement* element = component->model;
|
|
||||||
free(element);
|
|
||||||
free(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void js_widget_add_rbox(struct mjs* mjs) {
|
|
||||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
|
||||||
if(!check_arg_count(mjs, 5)) return;
|
|
||||||
|
|
||||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
|
||||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
|
||||||
int32_t w = mjs_get_int32(mjs, mjs_arg(mjs, 2));
|
|
||||||
int32_t h = mjs_get_int32(mjs, mjs_arg(mjs, 3));
|
|
||||||
int32_t r = mjs_get_int32(mjs, mjs_arg(mjs, 4));
|
|
||||||
|
|
||||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
|
||||||
component->draw = widget_rbox_draw;
|
|
||||||
component->free = widget_rbox_free;
|
|
||||||
component->model = malloc(sizeof(RboxElement));
|
|
||||||
RboxElement* element = component->model;
|
|
||||||
element->x = x;
|
|
||||||
element->y = y;
|
|
||||||
element->w = w;
|
|
||||||
element->h = h;
|
|
||||||
element->r = r;
|
|
||||||
|
|
||||||
with_view_model(
|
|
||||||
widget->view,
|
|
||||||
WidgetModel * model,
|
|
||||||
{
|
|
||||||
++model->max_assigned_id;
|
|
||||||
component->id = model->max_assigned_id;
|
|
||||||
ComponentArray_push_back(model->component, component);
|
|
||||||
},
|
|
||||||
true);
|
|
||||||
|
|
||||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_rframe_draw(Canvas* canvas, void* model) {
|
|
||||||
RframeElement* element = model;
|
|
||||||
canvas_draw_rframe(canvas, element->x, element->y, element->w, element->h, element->r);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_rframe_free(WidgetComponent* component) {
|
|
||||||
RframeElement* element = component->model;
|
|
||||||
free(element);
|
|
||||||
free(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void js_widget_add_rframe(struct mjs* mjs) {
|
|
||||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
|
||||||
if(!check_arg_count(mjs, 5)) return;
|
|
||||||
|
|
||||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
|
||||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
|
||||||
int32_t w = mjs_get_int32(mjs, mjs_arg(mjs, 2));
|
|
||||||
int32_t h = mjs_get_int32(mjs, mjs_arg(mjs, 3));
|
|
||||||
int32_t r = mjs_get_int32(mjs, mjs_arg(mjs, 4));
|
|
||||||
|
|
||||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
|
||||||
component->draw = widget_rframe_draw;
|
|
||||||
component->free = widget_rframe_free;
|
|
||||||
component->model = malloc(sizeof(RframeElement));
|
|
||||||
RframeElement* element = component->model;
|
|
||||||
element->x = x;
|
|
||||||
element->y = y;
|
|
||||||
element->w = w;
|
|
||||||
element->h = h;
|
|
||||||
element->r = r;
|
|
||||||
|
|
||||||
with_view_model(
|
|
||||||
widget->view,
|
|
||||||
WidgetModel * model,
|
|
||||||
{
|
|
||||||
++model->max_assigned_id;
|
|
||||||
component->id = model->max_assigned_id;
|
|
||||||
ComponentArray_push_back(model->component, component);
|
|
||||||
},
|
|
||||||
true);
|
|
||||||
|
|
||||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_text_draw(Canvas* canvas, void* model) {
|
|
||||||
TextElement* element = model;
|
|
||||||
canvas_set_font(canvas, element->font);
|
|
||||||
canvas_draw_str(canvas, element->x, element->y, furi_string_get_cstr(element->text));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_text_free(WidgetComponent* component) {
|
|
||||||
TextElement* element = component->model;
|
|
||||||
furi_string_free(element->text);
|
|
||||||
free(element);
|
|
||||||
free(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void js_widget_add_text(struct mjs* mjs) {
|
|
||||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
|
||||||
if(!check_arg_count(mjs, 4)) return;
|
|
||||||
|
|
||||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
|
||||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
|
||||||
|
|
||||||
mjs_val_t font_arg = mjs_arg(mjs, 2);
|
|
||||||
size_t font_name_len = 0;
|
|
||||||
const char* font_name_text = mjs_get_string(mjs, &font_arg, &font_name_len);
|
|
||||||
if(!font_name_text) {
|
|
||||||
ret_bad_args(mjs, "Font name must be a string");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Font font = FontTotalNumber;
|
|
||||||
size_t cmp_str_len = strlen("Primary");
|
|
||||||
if(font_name_len == cmp_str_len && strncmp(font_name_text, "Primary", cmp_str_len) == 0) {
|
|
||||||
font = FontPrimary;
|
|
||||||
} else {
|
|
||||||
cmp_str_len = strlen("Secondary");
|
|
||||||
if(font_name_len == cmp_str_len &&
|
|
||||||
strncmp(font_name_text, "Secondary", cmp_str_len) == 0) {
|
|
||||||
font = FontSecondary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(font == FontTotalNumber) {
|
|
||||||
ret_bad_args(mjs, "Unknown font name");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mjs_val_t text_arg = mjs_arg(mjs, 3);
|
|
||||||
size_t text_len = 0;
|
|
||||||
const char* text = mjs_get_string(mjs, &text_arg, &text_len);
|
|
||||||
if(!text) {
|
|
||||||
ret_bad_args(mjs, "Text must be a string");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
FuriString* text_str = furi_string_alloc_set(text);
|
|
||||||
|
|
||||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
|
||||||
component->draw = widget_text_draw;
|
|
||||||
component->free = widget_text_free;
|
|
||||||
component->model = malloc(sizeof(TextElement));
|
|
||||||
TextElement* element = component->model;
|
|
||||||
element->x = x;
|
|
||||||
element->y = y;
|
|
||||||
element->font = font;
|
|
||||||
element->text = text_str;
|
|
||||||
|
|
||||||
with_view_model(
|
|
||||||
widget->view,
|
|
||||||
WidgetModel * model,
|
|
||||||
{
|
|
||||||
++model->max_assigned_id;
|
|
||||||
component->id = model->max_assigned_id;
|
|
||||||
ComponentArray_push_back(model->component, component);
|
|
||||||
},
|
|
||||||
true);
|
|
||||||
|
|
||||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_xbm_draw(Canvas* canvas, void* model) {
|
|
||||||
XbmElement* element = model;
|
|
||||||
XbmImage* image = NULL;
|
|
||||||
|
|
||||||
with_view_model(
|
|
||||||
element->view,
|
|
||||||
WidgetModel * widget_model,
|
|
||||||
{ image = *XbmImageList_get(widget_model->image, element->index); },
|
|
||||||
false);
|
|
||||||
|
|
||||||
if(image) {
|
|
||||||
canvas_draw_xbm(canvas, element->x, element->y, image->width, image->height, image->data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_xbm_free(WidgetComponent* component) {
|
|
||||||
XbmElement* element = component->model;
|
|
||||||
free(element);
|
|
||||||
free(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void js_widget_add_xbm(struct mjs* mjs) {
|
|
||||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
|
||||||
if(!check_arg_count(mjs, 3)) return;
|
|
||||||
|
|
||||||
int32_t x = mjs_get_int32(mjs, mjs_arg(mjs, 0));
|
|
||||||
int32_t y = mjs_get_int32(mjs, mjs_arg(mjs, 1));
|
|
||||||
int32_t index = mjs_get_int32(mjs, mjs_arg(mjs, 2));
|
|
||||||
with_view_model(
|
|
||||||
widget->view,
|
|
||||||
WidgetModel * widget_model,
|
|
||||||
{
|
|
||||||
size_t count = XbmImageList_size(widget_model->image);
|
|
||||||
if(index < 0 || index >= (int32_t)count) {
|
|
||||||
ret_bad_args(mjs, "Invalid image index");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
false);
|
|
||||||
|
|
||||||
WidgetComponent* component = malloc(sizeof(WidgetComponent));
|
|
||||||
component->draw = widget_xbm_draw;
|
|
||||||
component->free = widget_xbm_free;
|
|
||||||
component->model = malloc(sizeof(XbmElement));
|
|
||||||
XbmElement* element = component->model;
|
|
||||||
element->x = x;
|
|
||||||
element->y = y;
|
|
||||||
element->index = index;
|
|
||||||
|
|
||||||
with_view_model(
|
|
||||||
widget->view,
|
|
||||||
WidgetModel * model,
|
|
||||||
{
|
|
||||||
++model->max_assigned_id;
|
|
||||||
component->id = model->max_assigned_id;
|
|
||||||
element->view = widget->view;
|
|
||||||
ComponentArray_push_back(model->component, component);
|
|
||||||
},
|
|
||||||
true);
|
|
||||||
|
|
||||||
mjs_return(mjs, mjs_mk_number(mjs, component->id));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void js_widget_is_open(struct mjs* mjs) {
|
|
||||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
|
||||||
if(!check_arg_count(mjs, 0)) return;
|
|
||||||
|
|
||||||
mjs_return(mjs, mjs_mk_boolean(mjs, widget->is_shown));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_callback(void* context, uint32_t arg) {
|
|
||||||
UNUSED(arg);
|
|
||||||
JsWidgetInst* widget = context;
|
|
||||||
view_holder_set_view(widget->view_holder, NULL);
|
|
||||||
widget->is_shown = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_exit(void* context) {
|
|
||||||
JsWidgetInst* widget = context;
|
|
||||||
// Using timer to schedule view_holder stop, will not work under high CPU load
|
|
||||||
furi_timer_pending_callback(widget_callback, widget, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void js_widget_show(struct mjs* mjs) {
|
|
||||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
|
||||||
if(!check_arg_count(mjs, 0)) return;
|
|
||||||
|
|
||||||
if(widget->is_shown) {
|
|
||||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Widget is already shown");
|
|
||||||
mjs_return(mjs, MJS_UNDEFINED);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
view_holder_set_view(widget->view_holder, widget->view);
|
|
||||||
widget->is_shown = true;
|
|
||||||
|
|
||||||
mjs_return(mjs, MJS_UNDEFINED);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void js_widget_close(struct mjs* mjs) {
|
|
||||||
JsWidgetInst* widget = get_this_ctx(mjs);
|
|
||||||
if(!check_arg_count(mjs, 0)) return;
|
|
||||||
|
|
||||||
view_holder_set_view(widget->view_holder, NULL);
|
|
||||||
widget->is_shown = false;
|
|
||||||
|
|
||||||
mjs_return(mjs, MJS_UNDEFINED);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void widget_draw_callback(Canvas* canvas, void* model) {
|
|
||||||
WidgetModel* widget_model = model;
|
|
||||||
canvas_clear(canvas);
|
|
||||||
|
|
||||||
ComponentArray_it_t it;
|
|
||||||
ComponentArray_it(it, widget_model->component);
|
|
||||||
while(!ComponentArray_end_p(it)) {
|
|
||||||
WidgetComponent* component = *ComponentArray_ref(it);
|
|
||||||
if(component->draw != NULL) {
|
|
||||||
component->draw(canvas, component->model);
|
|
||||||
}
|
|
||||||
ComponentArray_next(it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void* js_widget_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
|
||||||
UNUSED(modules);
|
|
||||||
JsWidgetInst* widget = malloc(sizeof(JsWidgetInst));
|
|
||||||
|
|
||||||
mjs_val_t widget_obj = mjs_mk_object(mjs);
|
|
||||||
mjs_set(mjs, widget_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, widget));
|
|
||||||
// addBox(x: number, y: number, w: number, h: number): number (returns id of the added component)
|
|
||||||
mjs_set(mjs, widget_obj, "addBox", ~0, MJS_MK_FN(js_widget_add_box));
|
|
||||||
// addCircle(x: number, y: number, r: number): number (returns id of the added component)
|
|
||||||
mjs_set(mjs, widget_obj, "addCircle", ~0, MJS_MK_FN(js_widget_add_circle));
|
|
||||||
// addDisc(x: number, y: number, r: number): number (returns id of the added component)
|
|
||||||
mjs_set(mjs, widget_obj, "addDisc", ~0, MJS_MK_FN(js_widget_add_disc));
|
|
||||||
// addDot(x: number, y: number): number (returns id of the added component)
|
|
||||||
mjs_set(mjs, widget_obj, "addDot", ~0, MJS_MK_FN(js_widget_add_dot));
|
|
||||||
// addFrame(x: number, y: number, w: number, h: number): number (returns id of the added component)
|
|
||||||
mjs_set(mjs, widget_obj, "addFrame", ~0, MJS_MK_FN(js_widget_add_frame));
|
|
||||||
// addGlyph(x: number, y: number, ch: number): number (returns id of the added component)
|
|
||||||
mjs_set(mjs, widget_obj, "addGlyph", ~0, MJS_MK_FN(js_widget_add_glyph));
|
|
||||||
// addLine(x1: number, y1: number, x2: number, y2: number): number (returns id of the added component)
|
|
||||||
mjs_set(mjs, widget_obj, "addLine", ~0, MJS_MK_FN(js_widget_add_line));
|
|
||||||
// addRbox(x: number, y: number, w: number, h: number, r: number): number (returns id of the added component)
|
|
||||||
mjs_set(mjs, widget_obj, "addRbox", ~0, MJS_MK_FN(js_widget_add_rbox));
|
|
||||||
// addRframe(x: number, y: number, w: number, h: number, r: number): number (returns id of the added component)
|
|
||||||
mjs_set(mjs, widget_obj, "addRframe", ~0, MJS_MK_FN(js_widget_add_rframe));
|
|
||||||
// addText(x: number, y: number, font: string, text: string): number (returns id of the added component)
|
|
||||||
mjs_set(mjs, widget_obj, "addText", ~0, MJS_MK_FN(js_widget_add_text));
|
|
||||||
// addXbm(x: number, y: number, index: number): number (returns id of the added component)
|
|
||||||
mjs_set(mjs, widget_obj, "addXbm", ~0, MJS_MK_FN(js_widget_add_xbm));
|
|
||||||
// loadImageXbm(path: string): number (returns index of the loaded image)
|
|
||||||
mjs_set(mjs, widget_obj, "loadImageXbm", ~0, MJS_MK_FN(js_widget_load_image_xbm));
|
|
||||||
// remove(id: number): boolean (returns true if the component was removed)
|
|
||||||
mjs_set(mjs, widget_obj, "remove", ~0, MJS_MK_FN(js_widget_remove));
|
|
||||||
// isOpen(): boolean (returns true if the widget is open)
|
|
||||||
mjs_set(mjs, widget_obj, "isOpen", ~0, MJS_MK_FN(js_widget_is_open));
|
|
||||||
// show(): void (shows the widget)
|
|
||||||
mjs_set(mjs, widget_obj, "show", ~0, MJS_MK_FN(js_widget_show));
|
|
||||||
// close(): void (closes the widget)
|
|
||||||
mjs_set(mjs, widget_obj, "close", ~0, MJS_MK_FN(js_widget_close));
|
|
||||||
|
|
||||||
widget->view = view_alloc();
|
|
||||||
view_allocate_model(widget->view, ViewModelTypeLockFree, sizeof(WidgetModel));
|
|
||||||
view_set_draw_callback(widget->view, widget_draw_callback);
|
|
||||||
with_view_model(
|
|
||||||
widget->view,
|
|
||||||
WidgetModel * model,
|
|
||||||
{
|
|
||||||
ComponentArray_init(model->component);
|
|
||||||
XbmImageList_init(model->image);
|
|
||||||
model->max_assigned_id = 0;
|
|
||||||
},
|
|
||||||
true);
|
|
||||||
|
|
||||||
Gui* gui = furi_record_open(RECORD_GUI);
|
|
||||||
widget->view_holder = view_holder_alloc();
|
|
||||||
view_holder_attach_to_gui(widget->view_holder, gui);
|
|
||||||
view_holder_set_back_callback(widget->view_holder, widget_exit, widget);
|
|
||||||
|
|
||||||
*object = widget_obj;
|
|
||||||
return widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void js_widget_destroy(void* inst) {
|
|
||||||
JsWidgetInst* widget = inst;
|
|
||||||
|
|
||||||
view_holder_set_view(widget->view_holder, NULL);
|
|
||||||
view_holder_free(widget->view_holder);
|
|
||||||
widget->view_holder = NULL;
|
|
||||||
|
|
||||||
furi_record_close(RECORD_GUI);
|
|
||||||
|
|
||||||
with_view_model(
|
|
||||||
widget->view,
|
|
||||||
WidgetModel * model,
|
|
||||||
{
|
|
||||||
ComponentArray_it_t it;
|
|
||||||
ComponentArray_it(it, model->component);
|
|
||||||
while(!ComponentArray_end_p(it)) {
|
|
||||||
WidgetComponent* component = *ComponentArray_ref(it);
|
|
||||||
if(component && component->free) {
|
|
||||||
component->free(component);
|
|
||||||
}
|
|
||||||
ComponentArray_next(it);
|
|
||||||
}
|
|
||||||
ComponentArray_reset(model->component);
|
|
||||||
ComponentArray_clear(model->component);
|
|
||||||
XbmImageList_clear(model->image);
|
|
||||||
},
|
|
||||||
false);
|
|
||||||
view_free(widget->view);
|
|
||||||
widget->view = NULL;
|
|
||||||
|
|
||||||
free(widget);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const JsModuleDescriptor js_widget_desc = {
|
|
||||||
"widget",
|
|
||||||
js_widget_create,
|
|
||||||
js_widget_destroy,
|
|
||||||
NULL,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const FlipperAppPluginDescriptor widget_plugin_descriptor = {
|
|
||||||
.appid = PLUGIN_APP_ID,
|
|
||||||
.ep_api_version = PLUGIN_API_VERSION,
|
|
||||||
.entry_point = &js_widget_desc,
|
|
||||||
};
|
|
||||||
|
|
||||||
const FlipperAppPluginDescriptor* js_widget_ep(void) {
|
|
||||||
return &widget_plugin_descriptor;
|
|
||||||
}
|
|
||||||
@@ -75,6 +75,34 @@ export interface Pin {
|
|||||||
* @version Added in JS SDK 0.1
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
interrupt(): Contract;
|
interrupt(): Contract;
|
||||||
|
/**
|
||||||
|
* Determines whether this pin supports PWM. If `false`, all other
|
||||||
|
* PWM-related methods on this pin will throw an error when called.
|
||||||
|
* @note On Flipper Zero only pins PA4 and PA7 support PWM
|
||||||
|
* @version Added in JS SDK 0.2, extra feature `"gpio-pwm"`
|
||||||
|
*/
|
||||||
|
isPwmSupported(): boolean;
|
||||||
|
/**
|
||||||
|
* Sets PWM parameters and starts the PWM. Configures the pin with
|
||||||
|
* `{ direction: "out", outMode: "push_pull" }`. Throws an error if PWM is
|
||||||
|
* not supported on this pin.
|
||||||
|
* @param freq Frequency in Hz
|
||||||
|
* @param duty Duty cycle in %
|
||||||
|
* @version Added in JS SDK 0.2, extra feature `"gpio-pwm"`
|
||||||
|
*/
|
||||||
|
pwmWrite(freq: number, duty: number): void;
|
||||||
|
/**
|
||||||
|
* Determines whether PWM is running. Throws an error if PWM is not
|
||||||
|
* supported on this pin.
|
||||||
|
* @version Added in JS SDK 0.2, extra feature `"gpio-pwm"`
|
||||||
|
*/
|
||||||
|
isPwmRunning(): boolean;
|
||||||
|
/**
|
||||||
|
* Stops PWM. Does not restore previous pin configuration. Throws an error
|
||||||
|
* if PWM is not supported on this pin.
|
||||||
|
* @version Added in JS SDK 0.2, extra feature `"gpio-pwm"`
|
||||||
|
*/
|
||||||
|
pwmStop(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -33,9 +33,10 @@ type Props = {
|
|||||||
length: number,
|
length: number,
|
||||||
defaultData: Uint8Array | ArrayBuffer,
|
defaultData: Uint8Array | ArrayBuffer,
|
||||||
}
|
}
|
||||||
declare class ByteInput extends View<Props> {
|
type Child = never;
|
||||||
|
declare class ByteInput extends View<Props, Child> {
|
||||||
input: Contract<string>;
|
input: Contract<string>;
|
||||||
}
|
}
|
||||||
declare class ByteInputFactory extends ViewFactory<Props, ByteInput> { }
|
declare class ByteInputFactory extends ViewFactory<Props, Child, ByteInput> { }
|
||||||
declare const factory: ByteInputFactory;
|
declare const factory: ByteInputFactory;
|
||||||
export = factory;
|
export = factory;
|
||||||
|
|||||||
@@ -37,9 +37,10 @@ type Props = {
|
|||||||
center: string,
|
center: string,
|
||||||
right: string,
|
right: string,
|
||||||
}
|
}
|
||||||
declare class Dialog extends View<Props> {
|
type Child = never;
|
||||||
|
declare class Dialog extends View<Props, Child> {
|
||||||
input: Contract<"left" | "center" | "right">;
|
input: Contract<"left" | "center" | "right">;
|
||||||
}
|
}
|
||||||
declare class DialogFactory extends ViewFactory<Props, Dialog> { }
|
declare class DialogFactory extends ViewFactory<Props, Child, Dialog> { }
|
||||||
declare const factory: DialogFactory;
|
declare const factory: DialogFactory;
|
||||||
export = factory;
|
export = factory;
|
||||||
|
|||||||
@@ -26,7 +26,8 @@
|
|||||||
import type { View, ViewFactory } from ".";
|
import type { View, ViewFactory } from ".";
|
||||||
|
|
||||||
type Props = {};
|
type Props = {};
|
||||||
declare class EmptyScreen extends View<Props> { }
|
type Child = never;
|
||||||
declare class EmptyScreenFactory extends ViewFactory<Props, EmptyScreen> { }
|
declare class EmptyScreen extends View<Props, Child> { }
|
||||||
|
declare class EmptyScreenFactory extends ViewFactory<Props, Child, EmptyScreen> { }
|
||||||
declare const factory: EmptyScreenFactory;
|
declare const factory: EmptyScreenFactory;
|
||||||
export = factory;
|
export = factory;
|
||||||
|
|||||||
11
applications/system/js_app/packages/fz-sdk/gui/icon.d.ts
vendored
Normal file
11
applications/system/js_app/packages/fz-sdk/gui/icon.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export type BuiltinIcon = "DolphinWait_59x54" | "js_script_10px";
|
||||||
|
|
||||||
|
export type IconData = symbol & { "__tag__": "icon" };
|
||||||
|
// introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a built-in firmware icon for use in GUI
|
||||||
|
* @param icon Name of the icon
|
||||||
|
* @version Added in JS SDK 0.2, extra feature `"gui-widget"`
|
||||||
|
*/
|
||||||
|
export declare function getBuiltin(icon: BuiltinIcon): IconData;
|
||||||
@@ -26,23 +26,23 @@
|
|||||||
* assumes control over the entire viewport and all input events. Different
|
* assumes control over the entire viewport and all input events. Different
|
||||||
* types of views are available (not all of which are unfortunately currently
|
* types of views are available (not all of which are unfortunately currently
|
||||||
* implemented in JS):
|
* implemented in JS):
|
||||||
* | View | Has JS adapter? |
|
* | View | Has JS adapter? |
|
||||||
* |----------------------|------------------|
|
* |----------------------|-----------------------|
|
||||||
* | `button_menu` | ❌ |
|
* | `button_menu` | ❌ |
|
||||||
* | `button_panel` | ❌ |
|
* | `button_panel` | ❌ |
|
||||||
* | `byte_input` | ✅ |
|
* | `byte_input` | ✅ |
|
||||||
* | `dialog_ex` | ✅ (as `dialog`) |
|
* | `dialog_ex` | ✅ (as `dialog`) |
|
||||||
* | `empty_screen` | ✅ |
|
* | `empty_screen` | ✅ |
|
||||||
* | `file_browser` | ❌ |
|
* | `file_browser` | ✅ (as `file_picker`) |
|
||||||
* | `loading` | ✅ |
|
* | `loading` | ✅ |
|
||||||
* | `menu` | ❌ |
|
* | `menu` | ❌ |
|
||||||
* | `number_input` | ❌ |
|
* | `number_input` | ❌ |
|
||||||
* | `popup` | ❌ |
|
* | `popup` | ❌ |
|
||||||
* | `submenu` | ✅ |
|
* | `submenu` | ✅ |
|
||||||
* | `text_box` | ✅ |
|
* | `text_box` | ✅ |
|
||||||
* | `text_input` | ✅ |
|
* | `text_input` | ✅ |
|
||||||
* | `variable_item_list` | ❌ |
|
* | `variable_item_list` | ❌ |
|
||||||
* | `widget` | ❌ |
|
* | `widget` | ✅ |
|
||||||
*
|
*
|
||||||
* In JS, each view has its own set of properties (or just "props"). The
|
* In JS, each view has its own set of properties (or just "props"). The
|
||||||
* programmer can manipulate these properties in two ways:
|
* programmer can manipulate these properties in two ways:
|
||||||
@@ -121,7 +121,7 @@ import type { Contract } from "../event_loop";
|
|||||||
|
|
||||||
type Properties = { [K: string]: any };
|
type Properties = { [K: string]: any };
|
||||||
|
|
||||||
export declare class View<Props extends Properties> {
|
export declare class View<Props extends Properties, Child> {
|
||||||
/**
|
/**
|
||||||
* Assign value to property by name
|
* Assign value to property by name
|
||||||
* @param property Name of the property
|
* @param property Name of the property
|
||||||
@@ -129,9 +129,26 @@ export declare class View<Props extends Properties> {
|
|||||||
* @version Added in JS SDK 0.1
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
set<P extends keyof Props>(property: P, value: Props[P]): void;
|
set<P extends keyof Props>(property: P, value: Props[P]): void;
|
||||||
|
/**
|
||||||
|
* Adds a child to the View
|
||||||
|
* @param child Child to add
|
||||||
|
* @version Added in JS SDK 0.2, extra feature `"gui-widget"`
|
||||||
|
*/
|
||||||
|
addChild<C extends Child>(child: C): void;
|
||||||
|
/**
|
||||||
|
* Removes all children from the View
|
||||||
|
* @version Added in JS SDK 0.2, extra feature `"gui-widget"`
|
||||||
|
*/
|
||||||
|
resetChildren(): void;
|
||||||
|
/**
|
||||||
|
* Removes all previous children from the View and assigns new children
|
||||||
|
* @param children The list of children to assign
|
||||||
|
* @version Added in JS SDK 0.2, extra feature `"gui-widget"`
|
||||||
|
*/
|
||||||
|
setChildren(children: Child[]): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare class ViewFactory<Props extends Properties, V extends View<Props>> {
|
export declare class ViewFactory<Props extends Properties, Child, V extends View<Props, Child>> {
|
||||||
/**
|
/**
|
||||||
* Create view instance with default values, can be changed later with set()
|
* Create view instance with default values, can be changed later with set()
|
||||||
* @version Added in JS SDK 0.1
|
* @version Added in JS SDK 0.1
|
||||||
@@ -140,9 +157,10 @@ export declare class ViewFactory<Props extends Properties, V extends View<Props>
|
|||||||
/**
|
/**
|
||||||
* Create view instance with custom values, can be changed later with set()
|
* Create view instance with custom values, can be changed later with set()
|
||||||
* @param initial Dictionary of property names to values
|
* @param initial Dictionary of property names to values
|
||||||
* @version Added in JS SDK 0.1
|
* @param children Optional list of children to add to the view
|
||||||
|
* @version Added in JS SDK 0.1; amended in JS SDK 0.2, extra feature `"gui-widget"`
|
||||||
*/
|
*/
|
||||||
makeWith(initial: Partial<Props>): V;
|
makeWith(initial: Partial<Props>, children?: Child[]): V;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -163,7 +181,7 @@ declare class ViewDispatcher {
|
|||||||
* View object currently shown
|
* View object currently shown
|
||||||
* @version Added in JS SDK 0.1
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
currentView: View<any>;
|
currentView: View<any, any>;
|
||||||
/**
|
/**
|
||||||
* Sends a number to the custom event handler
|
* Sends a number to the custom event handler
|
||||||
* @param event number to send
|
* @param event number to send
|
||||||
@@ -175,7 +193,7 @@ declare class ViewDispatcher {
|
|||||||
* @param assoc View-ViewDispatcher association as returned by `add`
|
* @param assoc View-ViewDispatcher association as returned by `add`
|
||||||
* @version Added in JS SDK 0.1
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
switchTo(assoc: View<any>): void;
|
switchTo(assoc: View<any, any>): void;
|
||||||
/**
|
/**
|
||||||
* Sends this ViewDispatcher to the front or back, above or below all other
|
* Sends this ViewDispatcher to the front or back, above or below all other
|
||||||
* GUI viewports
|
* GUI viewports
|
||||||
|
|||||||
@@ -27,7 +27,8 @@
|
|||||||
import type { View, ViewFactory } from ".";
|
import type { View, ViewFactory } from ".";
|
||||||
|
|
||||||
type Props = {};
|
type Props = {};
|
||||||
declare class Loading extends View<Props> { }
|
type Child = never;
|
||||||
declare class LoadingFactory extends ViewFactory<Props, Loading> { }
|
declare class Loading extends View<Props, Child> { }
|
||||||
|
declare class LoadingFactory extends ViewFactory<Props, Child, Loading> { }
|
||||||
declare const factory: LoadingFactory;
|
declare const factory: LoadingFactory;
|
||||||
export = factory;
|
export = factory;
|
||||||
|
|||||||
@@ -31,9 +31,10 @@ type Props = {
|
|||||||
header: string,
|
header: string,
|
||||||
items: string[],
|
items: string[],
|
||||||
};
|
};
|
||||||
declare class Submenu extends View<Props> {
|
type Child = never;
|
||||||
|
declare class Submenu extends View<Props, Child> {
|
||||||
chosen: Contract<number>;
|
chosen: Contract<number>;
|
||||||
}
|
}
|
||||||
declare class SubmenuFactory extends ViewFactory<Props, Submenu> { }
|
declare class SubmenuFactory extends ViewFactory<Props, Child, Submenu> { }
|
||||||
declare const factory: SubmenuFactory;
|
declare const factory: SubmenuFactory;
|
||||||
export = factory;
|
export = factory;
|
||||||
|
|||||||
@@ -33,9 +33,10 @@ type Props = {
|
|||||||
font: "text" | "hex",
|
font: "text" | "hex",
|
||||||
focus: "start" | "end",
|
focus: "start" | "end",
|
||||||
}
|
}
|
||||||
declare class TextBox extends View<Props> {
|
type Child = never;
|
||||||
|
declare class TextBox extends View<Props, Child> {
|
||||||
chosen: Contract<number>;
|
chosen: Contract<number>;
|
||||||
}
|
}
|
||||||
declare class TextBoxFactory extends ViewFactory<Props, TextBox> { }
|
declare class TextBoxFactory extends ViewFactory<Props, Child, TextBox> { }
|
||||||
declare const factory: TextBoxFactory;
|
declare const factory: TextBoxFactory;
|
||||||
export = factory;
|
export = factory;
|
||||||
|
|||||||
@@ -37,9 +37,10 @@ type Props = {
|
|||||||
defaultText: string,
|
defaultText: string,
|
||||||
defaultTextClear: boolean,
|
defaultTextClear: boolean,
|
||||||
}
|
}
|
||||||
declare class TextInput extends View<Props> {
|
type Child = never;
|
||||||
|
declare class TextInput extends View<Props, Child> {
|
||||||
input: Contract<string>;
|
input: Contract<string>;
|
||||||
}
|
}
|
||||||
declare class TextInputFactory extends ViewFactory<Props, TextInput> { }
|
declare class TextInputFactory extends ViewFactory<Props, Child, TextInput> { }
|
||||||
declare const factory: TextInputFactory;
|
declare const factory: TextInputFactory;
|
||||||
export = factory;
|
export = factory;
|
||||||
|
|||||||
66
applications/system/js_app/packages/fz-sdk/gui/widget.d.ts
vendored
Normal file
66
applications/system/js_app/packages/fz-sdk/gui/widget.d.ts
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* Displays a combination of custom elements on one screen.
|
||||||
|
*
|
||||||
|
* <img src="../images/widget.png" width="200" alt="Sample screenshot of the view" />
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* let eventLoop = require("event_loop");
|
||||||
|
* let gui = require("gui");
|
||||||
|
* let emptyView = require("gui/widget");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* This module depends on the `gui` module, which in turn depends on the
|
||||||
|
* `event_loop` module, so they _must_ be imported in this order. It is also
|
||||||
|
* recommended to conceptualize these modules first before using this one.
|
||||||
|
*
|
||||||
|
* # Example
|
||||||
|
* For an example refer to the GUI example.
|
||||||
|
*
|
||||||
|
* # View props
|
||||||
|
* This view does not have any props.
|
||||||
|
*
|
||||||
|
* # Children
|
||||||
|
* This view has the elements as its children.
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.2, extra feature `"gui-widget"`
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory } from ".";
|
||||||
|
import type { IconData } from "./icon";
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
|
||||||
|
type Position = { x: number, y: number };
|
||||||
|
type Size = { w: number, h: number };
|
||||||
|
type Alignment = { align: `${"t" | "c" | "b"}${"l" | "m" | "r"}` };
|
||||||
|
type Font = { font: "primary" | "secondary" | "keyboard" | "big_numbers" };
|
||||||
|
type Text = { text: string };
|
||||||
|
|
||||||
|
type StringMultilineElement = { element: "string_multiline" } & Position & Alignment & Font & Text;
|
||||||
|
type StringElement = { element: "string" } & Position & Alignment & Font & Text;
|
||||||
|
type TextBoxElement = { element: "text_box", stripToDots: boolean } & Position & Size & Alignment & Text;
|
||||||
|
type TextScrollElement = { element: "text_scroll" } & Position & Size & Text;
|
||||||
|
type ButtonElement = { element: "button", button: "left" | "center" | "right" } & Text;
|
||||||
|
type IconElement = { element: "icon", iconData: IconData } & Position;
|
||||||
|
type FrameElement = { element: "frame", radius: number } & Position & Size;
|
||||||
|
|
||||||
|
type Element = StringMultilineElement
|
||||||
|
| StringElement
|
||||||
|
| TextBoxElement
|
||||||
|
| TextScrollElement
|
||||||
|
| ButtonElement
|
||||||
|
| IconElement
|
||||||
|
| FrameElement;
|
||||||
|
|
||||||
|
type Props = {};
|
||||||
|
type Child = Element;
|
||||||
|
declare class Widget extends View<Props, Child> {
|
||||||
|
/**
|
||||||
|
* Event source for buttons. Only gets fired if there's a corresponding
|
||||||
|
* button element.
|
||||||
|
*/
|
||||||
|
button: Contract<"left" | "center" | "right">;
|
||||||
|
}
|
||||||
|
declare class WidgetFactory extends ViewFactory<Props, Child, Widget> { }
|
||||||
|
declare const factory: WidgetFactory;
|
||||||
|
export = factory;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@darkflippers/fz-sdk-ul",
|
"name": "@darkflippers/fz-sdk-ul",
|
||||||
"version": "0.1.3",
|
"version": "0.2.0",
|
||||||
"description": "Type declarations and documentation for native JS modules available on Unleashed Custom Firmware for Flipper Zero",
|
"description": "Type declarations and documentation for native JS modules available on Unleashed Custom Firmware for Flipper Zero",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"unleashed",
|
"unleashed",
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
/**
|
|
||||||
* Displays a customizable Widget on screen
|
|
||||||
* @version Available with JS feature `widget`
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
|
|
||||||
type ComponentId = number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Add a box component
|
|
||||||
* @param x Horizontal position
|
|
||||||
* @param y Vertical position
|
|
||||||
* @param w Width
|
|
||||||
* @param h Height
|
|
||||||
*/
|
|
||||||
export declare function addBox(x: number, y: number, w: number, h: number): ComponentId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Add a circle component
|
|
||||||
* @param x Horizontal position
|
|
||||||
* @param y Vertical position
|
|
||||||
* @param r Radius
|
|
||||||
*/
|
|
||||||
export declare function addCircle(x: number, y: number, r: number): ComponentId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Add a disc component
|
|
||||||
* @param x Horizontal position
|
|
||||||
* @param y Vertical position
|
|
||||||
* @param r Radius
|
|
||||||
*/
|
|
||||||
export declare function addDisc(x: number, y: number, r: number): ComponentId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Add a dot component
|
|
||||||
* @param x Horizontal position
|
|
||||||
* @param y Vertical position
|
|
||||||
*/
|
|
||||||
export declare function addDot(x: number, y: number): ComponentId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Add a frame component
|
|
||||||
* @param x Horizontal position
|
|
||||||
* @param y Vertical position
|
|
||||||
* @param w Width
|
|
||||||
* @param h Height
|
|
||||||
*/
|
|
||||||
export declare function addFrame(x: number, y: number, w: number, h: number): ComponentId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Add a glyph component
|
|
||||||
* @param x Horizontal position
|
|
||||||
* @param y Vertical position
|
|
||||||
* @param ch ASCII character code (eg. `"C".charCodeAt(0)`)
|
|
||||||
*/
|
|
||||||
export declare function addGlyph(x: number, y: number, ch: number): ComponentId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Add a line component
|
|
||||||
* @param x1 Horizontal position 1
|
|
||||||
* @param y1 Vertical position 1
|
|
||||||
* @param x2 Horizontal position 2
|
|
||||||
* @param y2 Vertical position 2
|
|
||||||
*/
|
|
||||||
export declare function addLine(x1: number, y1: number, x2: number, y2: number): ComponentId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Add a rounded box component
|
|
||||||
* @param x Horizontal position
|
|
||||||
* @param y Vertical position
|
|
||||||
* @param w Width
|
|
||||||
* @param h Height
|
|
||||||
* @param r Radius
|
|
||||||
*/
|
|
||||||
export declare function addRbox(x: number, y: number, w: number, h: number, r: number): ComponentId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Add a rounded frame component
|
|
||||||
* @param x Horizontal position
|
|
||||||
* @param y Vertical position
|
|
||||||
* @param w Width
|
|
||||||
* @param h Height
|
|
||||||
* @param r Radius
|
|
||||||
*/
|
|
||||||
export declare function addRframe(x: number, y: number, w: number, h: number, r: number): ComponentId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Add a text component
|
|
||||||
* @param x Horizontal position
|
|
||||||
* @param y Vertical position
|
|
||||||
* @param font What font to use, Primary or Secondary
|
|
||||||
* @param text Text to display
|
|
||||||
*/
|
|
||||||
export declare function addText(x: number, y: number, font: "Primary" | "Secondary", text: string): ComponentId;
|
|
||||||
|
|
||||||
type XbmId = number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Add an xbm image component
|
|
||||||
* @param x Horizontal position
|
|
||||||
* @param y Vertical position
|
|
||||||
* @param index Loaded xbm id to use
|
|
||||||
*/
|
|
||||||
export declare function addXbm(x: number, y: number, index: XbmId): ComponentId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Load an xbm image sprite
|
|
||||||
* @param path Xbm file to load
|
|
||||||
*/
|
|
||||||
export declare function loadImageXbm(path: string): XbmId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Remove a component
|
|
||||||
* @param id Component id to remove
|
|
||||||
*/
|
|
||||||
export declare function remove(id: ComponentId): boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Check if the widget view is shown
|
|
||||||
*/
|
|
||||||
export declare function isOpen(): boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Show the widget view
|
|
||||||
*/
|
|
||||||
export declare function show(): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Close the widget view
|
|
||||||
*/
|
|
||||||
export declare function close(): void;
|
|
||||||
BIN
documentation/images/widget.png
Normal file
BIN
documentation/images/widget.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
@@ -27,23 +27,23 @@ always access the canvas through a viewport.
|
|||||||
In Flipper's terminology, a "View" is a fullscreen design element that assumes
|
In Flipper's terminology, a "View" is a fullscreen design element that assumes
|
||||||
control over the entire viewport and all input events. Different types of views
|
control over the entire viewport and all input events. Different types of views
|
||||||
are available (not all of which are unfortunately currently implemented in JS):
|
are available (not all of which are unfortunately currently implemented in JS):
|
||||||
| View | Has JS adapter? |
|
| View | Has JS adapter? |
|
||||||
|----------------------|------------------|
|
|----------------------|-----------------------|
|
||||||
| `button_menu` | ❌ |
|
| `button_menu` | ❌ |
|
||||||
| `button_panel` | ❌ |
|
| `button_panel` | ❌ |
|
||||||
| `byte_input` | ❌ |
|
| `byte_input` | ✅ |
|
||||||
| `dialog_ex` | ✅ (as `dialog`) |
|
| `dialog_ex` | ✅ (as `dialog`) |
|
||||||
| `empty_screen` | ✅ |
|
| `empty_screen` | ✅ |
|
||||||
| `file_browser` | ❌ |
|
| `file_browser` | ✅ (as `file_picker`) |
|
||||||
| `loading` | ✅ |
|
| `loading` | ✅ |
|
||||||
| `menu` | ❌ |
|
| `menu` | ❌ |
|
||||||
| `number_input` | ❌ |
|
| `number_input` | ❌ |
|
||||||
| `popup` | ❌ |
|
| `popup` | ❌ |
|
||||||
| `submenu` | ✅ |
|
| `submenu` | ✅ |
|
||||||
| `text_box` | ✅ |
|
| `text_box` | ✅ |
|
||||||
| `text_input` | ✅ |
|
| `text_input` | ✅ |
|
||||||
| `variable_item_list` | ❌ |
|
| `variable_item_list` | ❌ |
|
||||||
| `widget` | ❌ |
|
| `widget` | ✅ |
|
||||||
|
|
||||||
In JS, each view has its own set of properties (or just "props"). The programmer
|
In JS, each view has its own set of properties (or just "props"). The programmer
|
||||||
can manipulate these properties in two ways:
|
can manipulate these properties in two ways:
|
||||||
|
|||||||
25
documentation/js/js_gui__widget.md
Normal file
25
documentation/js/js_gui__widget.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# js_gui__widget {#js_gui__widget}
|
||||||
|
|
||||||
|
# Widget GUI view
|
||||||
|
Displays a combination of custom elements on one screen
|
||||||
|
|
||||||
|
<img src="widget.png" width="200" alt="Sample screenshot of the view" />
|
||||||
|
|
||||||
|
```js
|
||||||
|
let eventLoop = require("event_loop");
|
||||||
|
let gui = require("gui");
|
||||||
|
let widgetView = require("gui/widget");
|
||||||
|
```
|
||||||
|
|
||||||
|
This module depends on the `gui` module, which in turn depends on the
|
||||||
|
`event_loop` module, so they _must_ be imported in this order. It is also
|
||||||
|
recommended to conceptualize these modules first before using this one.
|
||||||
|
|
||||||
|
# Example
|
||||||
|
For an example refer to the `gui.js` example script.
|
||||||
|
|
||||||
|
# View props
|
||||||
|
This view does not have any props.
|
||||||
|
|
||||||
|
# Children
|
||||||
|
This view has the elements as its children.
|
||||||
@@ -584,7 +584,8 @@ static void mjs_apply_(struct mjs* mjs) {
|
|||||||
if(mjs_is_array(v)) {
|
if(mjs_is_array(v)) {
|
||||||
nargs = mjs_array_length(mjs, v);
|
nargs = mjs_array_length(mjs, v);
|
||||||
args = calloc(nargs, sizeof(args[0]));
|
args = calloc(nargs, sizeof(args[0]));
|
||||||
for(i = 0; i < nargs; i++) args[i] = mjs_array_get(mjs, v, i);
|
for(i = 0; i < nargs; i++)
|
||||||
|
args[i] = mjs_array_get(mjs, v, i);
|
||||||
}
|
}
|
||||||
mjs_apply(mjs, &res, func, mjs_arg(mjs, 0), nargs, args);
|
mjs_apply(mjs, &res, func, mjs_arg(mjs, 0), nargs, args);
|
||||||
free(args);
|
free(args);
|
||||||
|
|||||||
@@ -4,6 +4,46 @@
|
|||||||
|
|
||||||
#define MF_DESFIRE_PROTOCOL_NAME "Mifare DESFire"
|
#define MF_DESFIRE_PROTOCOL_NAME "Mifare DESFire"
|
||||||
|
|
||||||
|
#define MF_DESFIRE_HW_MINOR_TYPE (0x00)
|
||||||
|
#define MF_DESFIRE_HW_MINOR_TYPE_MF3ICD40 (0x02)
|
||||||
|
|
||||||
|
#define MF_DESFIRE_HW_MAJOR_TYPE_EV1 (0x01)
|
||||||
|
#define MF_DESFIRE_HW_MAJOR_TYPE_EV2 (0x12)
|
||||||
|
#define MF_DESFIRE_HW_MAJOR_TYPE_EV2_XL (0x22)
|
||||||
|
#define MF_DESFIRE_HW_MAJOR_TYPE_EV3 (0x33)
|
||||||
|
#define MF_DESFIRE_HW_MAJOR_TYPE_MF3ICD40 (0x00)
|
||||||
|
|
||||||
|
#define MF_DESFIRE_STORAGE_SIZE_2K (0x16)
|
||||||
|
#define MF_DESFIRE_STORAGE_SIZE_4K (0x18)
|
||||||
|
#define MF_DESFIRE_STORAGE_SIZE_8K (0x1A)
|
||||||
|
#define MF_DESFIRE_STORAGE_SIZE_16K (0x1C)
|
||||||
|
#define MF_DESFIRE_STORAGE_SIZE_32K (0x1E)
|
||||||
|
#define MF_DESFIRE_STORAGE_SIZE_MF3ICD40 (0xFF)
|
||||||
|
#define MF_DESFIRE_STORAGE_SIZE_UNKNOWN (0xFF)
|
||||||
|
|
||||||
|
#define MF_DESFIRE_TEST_TYPE_MF3ICD40(major, minor, storage) \
|
||||||
|
(((major) == MF_DESFIRE_HW_MAJOR_TYPE_MF3ICD40) && \
|
||||||
|
((minor) == MF_DESFIRE_HW_MINOR_TYPE_MF3ICD40) && \
|
||||||
|
((storage) == MF_DESFIRE_STORAGE_SIZE_MF3ICD40))
|
||||||
|
|
||||||
|
static const char* mf_desfire_type_strings[] = {
|
||||||
|
[MfDesfireTypeMF3ICD40] = "(MF3ICD40)",
|
||||||
|
[MfDesfireTypeEV1] = "EV1",
|
||||||
|
[MfDesfireTypeEV2] = "EV2",
|
||||||
|
[MfDesfireTypeEV2XL] = "EV2 XL",
|
||||||
|
[MfDesfireTypeEV3] = "EV3",
|
||||||
|
[MfDesfireTypeUnknown] = "UNK",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char* mf_desfire_size_strings[] = {
|
||||||
|
[MfDesfireSize2k] = "2K",
|
||||||
|
[MfDesfireSize4k] = "4K",
|
||||||
|
[MfDesfireSize8k] = "8K",
|
||||||
|
[MfDesfireSize16k] = "16K",
|
||||||
|
[MfDesfireSize32k] = "32K",
|
||||||
|
[MfDesfireSizeUnknown] = "",
|
||||||
|
};
|
||||||
|
|
||||||
const NfcDeviceBase nfc_device_mf_desfire = {
|
const NfcDeviceBase nfc_device_mf_desfire = {
|
||||||
.protocol_name = MF_DESFIRE_PROTOCOL_NAME,
|
.protocol_name = MF_DESFIRE_PROTOCOL_NAME,
|
||||||
.alloc = (NfcDeviceAlloc)mf_desfire_alloc,
|
.alloc = (NfcDeviceAlloc)mf_desfire_alloc,
|
||||||
@@ -26,7 +66,7 @@ MfDesfireData* mf_desfire_alloc(void) {
|
|||||||
data->master_key_versions = simple_array_alloc(&mf_desfire_key_version_array_config);
|
data->master_key_versions = simple_array_alloc(&mf_desfire_key_version_array_config);
|
||||||
data->application_ids = simple_array_alloc(&mf_desfire_app_id_array_config);
|
data->application_ids = simple_array_alloc(&mf_desfire_app_id_array_config);
|
||||||
data->applications = simple_array_alloc(&mf_desfire_application_array_config);
|
data->applications = simple_array_alloc(&mf_desfire_application_array_config);
|
||||||
|
data->device_name = furi_string_alloc();
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +78,7 @@ void mf_desfire_free(MfDesfireData* data) {
|
|||||||
simple_array_free(data->application_ids);
|
simple_array_free(data->application_ids);
|
||||||
simple_array_free(data->master_key_versions);
|
simple_array_free(data->master_key_versions);
|
||||||
iso14443_4a_free(data->iso14443_4a_data);
|
iso14443_4a_free(data->iso14443_4a_data);
|
||||||
|
furi_string_free(data->device_name);
|
||||||
free(data);
|
free(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,10 +269,83 @@ bool mf_desfire_is_equal(const MfDesfireData* data, const MfDesfireData* other)
|
|||||||
simple_array_is_equal(data->applications, other->applications);
|
simple_array_is_equal(data->applications, other->applications);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static MfDesfireType mf_desfire_get_type_from_version(const MfDesfireVersion* const version) {
|
||||||
|
MfDesfireType type = MfDesfireTypeUnknown;
|
||||||
|
|
||||||
|
switch(version->hw_major) {
|
||||||
|
case MF_DESFIRE_HW_MAJOR_TYPE_EV1:
|
||||||
|
type = MfDesfireTypeEV1;
|
||||||
|
break;
|
||||||
|
case MF_DESFIRE_HW_MAJOR_TYPE_EV2:
|
||||||
|
type = MfDesfireTypeEV2;
|
||||||
|
break;
|
||||||
|
case MF_DESFIRE_HW_MAJOR_TYPE_EV2_XL:
|
||||||
|
type = MfDesfireTypeEV2XL;
|
||||||
|
break;
|
||||||
|
case MF_DESFIRE_HW_MAJOR_TYPE_EV3:
|
||||||
|
type = MfDesfireTypeEV3;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if(MF_DESFIRE_TEST_TYPE_MF3ICD40(version->hw_major, version->hw_minor, version->hw_storage))
|
||||||
|
type = MfDesfireTypeMF3ICD40;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MfDesfireSize mf_desfire_get_size_from_version(const MfDesfireVersion* const version) {
|
||||||
|
MfDesfireSize size = MfDesfireSizeUnknown;
|
||||||
|
|
||||||
|
switch(version->hw_storage) {
|
||||||
|
case MF_DESFIRE_STORAGE_SIZE_2K:
|
||||||
|
size = MfDesfireSize2k;
|
||||||
|
break;
|
||||||
|
case MF_DESFIRE_STORAGE_SIZE_4K:
|
||||||
|
size = MfDesfireSize4k;
|
||||||
|
break;
|
||||||
|
case MF_DESFIRE_STORAGE_SIZE_8K:
|
||||||
|
size = MfDesfireSize8k;
|
||||||
|
break;
|
||||||
|
case MF_DESFIRE_STORAGE_SIZE_16K:
|
||||||
|
size = MfDesfireSize16k;
|
||||||
|
break;
|
||||||
|
case MF_DESFIRE_STORAGE_SIZE_32K:
|
||||||
|
size = MfDesfireSize32k;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if(MF_DESFIRE_TEST_TYPE_MF3ICD40(version->hw_major, version->hw_minor, version->hw_storage))
|
||||||
|
size = MfDesfireSize4k;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
const char* mf_desfire_get_device_name(const MfDesfireData* data, NfcDeviceNameType name_type) {
|
const char* mf_desfire_get_device_name(const MfDesfireData* data, NfcDeviceNameType name_type) {
|
||||||
UNUSED(data);
|
furi_check(data);
|
||||||
UNUSED(name_type);
|
|
||||||
return MF_DESFIRE_PROTOCOL_NAME;
|
const MfDesfireType type = mf_desfire_get_type_from_version(&data->version);
|
||||||
|
const MfDesfireSize size = mf_desfire_get_size_from_version(&data->version);
|
||||||
|
|
||||||
|
if(type == MfDesfireTypeUnknown) {
|
||||||
|
furi_string_printf(data->device_name, "Unknown %s", MF_DESFIRE_PROTOCOL_NAME);
|
||||||
|
} else if(name_type == NfcDeviceNameTypeFull) {
|
||||||
|
furi_string_printf(
|
||||||
|
data->device_name,
|
||||||
|
"%s %s %s",
|
||||||
|
MF_DESFIRE_PROTOCOL_NAME,
|
||||||
|
mf_desfire_type_strings[type],
|
||||||
|
mf_desfire_size_strings[size]);
|
||||||
|
} else {
|
||||||
|
furi_string_printf(
|
||||||
|
data->device_name,
|
||||||
|
"%s %s",
|
||||||
|
mf_desfire_type_strings[type],
|
||||||
|
mf_desfire_size_strings[size]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return furi_string_get_cstr(data->device_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t* mf_desfire_get_uid(const MfDesfireData* data, size_t* uid_len) {
|
const uint8_t* mf_desfire_get_uid(const MfDesfireData* data, size_t* uid_len) {
|
||||||
|
|||||||
@@ -29,6 +29,28 @@ extern "C" {
|
|||||||
#define MF_DESFIRE_APP_ID_SIZE (3)
|
#define MF_DESFIRE_APP_ID_SIZE (3)
|
||||||
#define MF_DESFIRE_VALUE_SIZE (4)
|
#define MF_DESFIRE_VALUE_SIZE (4)
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
MfDesfireTypeMF3ICD40,
|
||||||
|
MfDesfireTypeEV1,
|
||||||
|
MfDesfireTypeEV2,
|
||||||
|
MfDesfireTypeEV2XL,
|
||||||
|
MfDesfireTypeEV3,
|
||||||
|
|
||||||
|
MfDesfireTypeUnknown,
|
||||||
|
MfDesfireTypeNum,
|
||||||
|
} MfDesfireType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
MfDesfireSize2k,
|
||||||
|
MfDesfireSize4k,
|
||||||
|
MfDesfireSize8k,
|
||||||
|
MfDesfireSize16k,
|
||||||
|
MfDesfireSize32k,
|
||||||
|
|
||||||
|
MfDesfireSizeUnknown,
|
||||||
|
MfDesfireSizeNum,
|
||||||
|
} MfDesfireSize;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t hw_vendor;
|
uint8_t hw_vendor;
|
||||||
uint8_t hw_type;
|
uint8_t hw_type;
|
||||||
@@ -131,6 +153,7 @@ typedef enum {
|
|||||||
MfDesfireErrorProtocol,
|
MfDesfireErrorProtocol,
|
||||||
MfDesfireErrorTimeout,
|
MfDesfireErrorTimeout,
|
||||||
MfDesfireErrorAuthentication,
|
MfDesfireErrorAuthentication,
|
||||||
|
MfDesfireErrorCommandNotSupported,
|
||||||
} MfDesfireError;
|
} MfDesfireError;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -141,6 +164,7 @@ typedef struct {
|
|||||||
SimpleArray* master_key_versions;
|
SimpleArray* master_key_versions;
|
||||||
SimpleArray* application_ids;
|
SimpleArray* application_ids;
|
||||||
SimpleArray* applications;
|
SimpleArray* applications;
|
||||||
|
FuriString* device_name;
|
||||||
} MfDesfireData;
|
} MfDesfireData;
|
||||||
|
|
||||||
extern const NfcDeviceBase nfc_device_mf_desfire;
|
extern const NfcDeviceBase nfc_device_mf_desfire;
|
||||||
|
|||||||
@@ -82,9 +82,12 @@ static NfcCommand mf_desfire_poller_handler_read_free_memory(MfDesfirePoller* in
|
|||||||
FURI_LOG_D(TAG, "Read free memory success");
|
FURI_LOG_D(TAG, "Read free memory success");
|
||||||
instance->state = MfDesfirePollerStateReadMasterKeySettings;
|
instance->state = MfDesfirePollerStateReadMasterKeySettings;
|
||||||
} else if(instance->error == MfDesfireErrorNotPresent) {
|
} else if(instance->error == MfDesfireErrorNotPresent) {
|
||||||
FURI_LOG_D(TAG, "Read free memoty is unsupported");
|
FURI_LOG_D(TAG, "Read free memory is not present");
|
||||||
instance->state = MfDesfirePollerStateReadMasterKeySettings;
|
instance->state = MfDesfirePollerStateReadMasterKeySettings;
|
||||||
command = NfcCommandReset;
|
command = NfcCommandReset;
|
||||||
|
} else if(instance->error == MfDesfireErrorCommandNotSupported) {
|
||||||
|
FURI_LOG_D(TAG, "Read free memory is unsupported");
|
||||||
|
instance->state = MfDesfirePollerStateReadMasterKeySettings;
|
||||||
} else {
|
} else {
|
||||||
FURI_LOG_E(TAG, "Failed to read free memory");
|
FURI_LOG_E(TAG, "Failed to read free memory");
|
||||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ MfDesfireError mf_desfire_process_status_code(uint8_t status_code) {
|
|||||||
return MfDesfireErrorNone;
|
return MfDesfireErrorNone;
|
||||||
case MF_DESFIRE_STATUS_AUTHENTICATION_ERROR:
|
case MF_DESFIRE_STATUS_AUTHENTICATION_ERROR:
|
||||||
return MfDesfireErrorAuthentication;
|
return MfDesfireErrorAuthentication;
|
||||||
|
case MF_DESFIRE_STATUS_ILLEGAL_COMMAND_CODE:
|
||||||
|
return MfDesfireErrorCommandNotSupported;
|
||||||
default:
|
default:
|
||||||
return MfDesfireErrorProtocol;
|
return MfDesfireErrorProtocol;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ typedef struct {
|
|||||||
FuriThreadId thread_id;
|
FuriThreadId thread_id;
|
||||||
MfUltralightError error;
|
MfUltralightError error;
|
||||||
MfUltralightPollerContextData data;
|
MfUltralightPollerContextData data;
|
||||||
|
const MfUltralightPollerAuthContext* auth_context;
|
||||||
} MfUltralightPollerContext;
|
} MfUltralightPollerContext;
|
||||||
|
|
||||||
typedef MfUltralightError (*MfUltralightPollerCmdHandler)(
|
typedef MfUltralightError (*MfUltralightPollerCmdHandler)(
|
||||||
@@ -250,12 +251,17 @@ static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void
|
|||||||
poller_context->error = mfu_event->data->error;
|
poller_context->error = mfu_event->data->error;
|
||||||
command = NfcCommandStop;
|
command = NfcCommandStop;
|
||||||
} else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) {
|
} else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) {
|
||||||
mfu_event->data->auth_context.skip_auth = true;
|
if(poller_context->auth_context != NULL) {
|
||||||
if(mf_ultralight_support_feature(
|
mfu_event->data->auth_context = *poller_context->auth_context;
|
||||||
mfu_poller->feature_set, MfUltralightFeatureSupportAuthenticate)) {
|
} else {
|
||||||
mfu_event->data->auth_context.skip_auth = false;
|
mfu_event->data->auth_context.skip_auth = true;
|
||||||
memset(
|
if(mfu_poller->data->type == MfUltralightTypeMfulC) {
|
||||||
mfu_poller->auth_context.tdes_key.data, 0x00, MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE);
|
mfu_event->data->auth_context.skip_auth = false;
|
||||||
|
memset(
|
||||||
|
mfu_poller->auth_context.tdes_key.data,
|
||||||
|
0x00,
|
||||||
|
MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,13 +272,17 @@ static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void
|
|||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
MfUltralightError mf_ultralight_poller_sync_read_card(Nfc* nfc, MfUltralightData* data) {
|
MfUltralightError mf_ultralight_poller_sync_read_card(
|
||||||
|
Nfc* nfc,
|
||||||
|
MfUltralightData* data,
|
||||||
|
const MfUltralightPollerAuthContext* auth_context) {
|
||||||
furi_check(nfc);
|
furi_check(nfc);
|
||||||
furi_check(data);
|
furi_check(data);
|
||||||
|
|
||||||
MfUltralightPollerContext poller_context = {};
|
MfUltralightPollerContext poller_context = {};
|
||||||
poller_context.thread_id = furi_thread_get_current_id();
|
poller_context.thread_id = furi_thread_get_current_id();
|
||||||
poller_context.data.data = mf_ultralight_alloc();
|
poller_context.data.data = mf_ultralight_alloc();
|
||||||
|
poller_context.auth_context = auth_context;
|
||||||
|
|
||||||
NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolMfUltralight);
|
NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolMfUltralight);
|
||||||
nfc_poller_start(poller, mf_ultralight_poller_read_callback, &poller_context);
|
nfc_poller_start(poller, mf_ultralight_poller_read_callback, &poller_context);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "mf_ultralight.h"
|
#include "mf_ultralight.h"
|
||||||
|
#include "mf_ultralight_poller.h"
|
||||||
#include <nfc/nfc.h>
|
#include <nfc/nfc.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
@@ -27,7 +28,10 @@ MfUltralightError mf_ultralight_poller_sync_read_tearing_flag(
|
|||||||
uint8_t flag_num,
|
uint8_t flag_num,
|
||||||
MfUltralightTearingFlag* data);
|
MfUltralightTearingFlag* data);
|
||||||
|
|
||||||
MfUltralightError mf_ultralight_poller_sync_read_card(Nfc* nfc, MfUltralightData* data);
|
MfUltralightError mf_ultralight_poller_sync_read_card(
|
||||||
|
Nfc* nfc,
|
||||||
|
MfUltralightData* data,
|
||||||
|
const MfUltralightPollerAuthContext* auth_context);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ static NfcCommand st25tb_poller_request_mode_handler(St25tbPoller* instance) {
|
|||||||
St25tbPollerEventDataModeRequest* mode_request_data =
|
St25tbPollerEventDataModeRequest* mode_request_data =
|
||||||
&instance->st25tb_event_data.mode_request;
|
&instance->st25tb_event_data.mode_request;
|
||||||
|
|
||||||
furi_assert(mode_request_data->mode < St25tbPollerModeNum);
|
furi_check(mode_request_data->mode < St25tbPollerModeNum);
|
||||||
|
|
||||||
if(mode_request_data->mode == St25tbPollerModeRead) {
|
if(mode_request_data->mode == St25tbPollerModeRead) {
|
||||||
instance->state = St25tbPollerStateRead;
|
instance->state = St25tbPollerStateRead;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
entry,status,name,type,params
|
entry,status,name,type,params
|
||||||
Version,+,79.3,,
|
Version,+,80.1,,
|
||||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||||
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
||||||
Header,+,applications/services/cli/cli.h,,
|
Header,+,applications/services/cli/cli.h,,
|
||||||
|
|||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <furi.h>
|
#include <furi.h>
|
||||||
#include <furi_hal_adc.h>
|
#include <furi_hal_adc.h>
|
||||||
|
#include <furi_hal_pwm.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -40,6 +41,7 @@ typedef struct {
|
|||||||
const GpioPin* pin;
|
const GpioPin* pin;
|
||||||
const char* name;
|
const char* name;
|
||||||
const FuriHalAdcChannel channel;
|
const FuriHalAdcChannel channel;
|
||||||
|
const FuriHalPwmOutputId pwm_output;
|
||||||
const uint8_t number;
|
const uint8_t number;
|
||||||
const bool debug;
|
const bool debug;
|
||||||
} GpioPinRecord;
|
} GpioPinRecord;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
entry,status,name,type,params
|
entry,status,name,type,params
|
||||||
Version,+,79.4,,
|
Version,+,80.1,,
|
||||||
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
|
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
|
||||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||||
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
||||||
@@ -2731,7 +2731,7 @@ Function,+,mf_ultralight_poller_read_page_from_sector,MfUltralightError,"MfUltra
|
|||||||
Function,+,mf_ultralight_poller_read_signature,MfUltralightError,"MfUltralightPoller*, MfUltralightSignature*"
|
Function,+,mf_ultralight_poller_read_signature,MfUltralightError,"MfUltralightPoller*, MfUltralightSignature*"
|
||||||
Function,+,mf_ultralight_poller_read_tearing_flag,MfUltralightError,"MfUltralightPoller*, uint8_t, MfUltralightTearingFlag*"
|
Function,+,mf_ultralight_poller_read_tearing_flag,MfUltralightError,"MfUltralightPoller*, uint8_t, MfUltralightTearingFlag*"
|
||||||
Function,+,mf_ultralight_poller_read_version,MfUltralightError,"MfUltralightPoller*, MfUltralightVersion*"
|
Function,+,mf_ultralight_poller_read_version,MfUltralightError,"MfUltralightPoller*, MfUltralightVersion*"
|
||||||
Function,+,mf_ultralight_poller_sync_read_card,MfUltralightError,"Nfc*, MfUltralightData*"
|
Function,+,mf_ultralight_poller_sync_read_card,MfUltralightError,"Nfc*, MfUltralightData*, const MfUltralightPollerAuthContext*"
|
||||||
Function,+,mf_ultralight_poller_sync_read_counter,MfUltralightError,"Nfc*, uint8_t, MfUltralightCounter*"
|
Function,+,mf_ultralight_poller_sync_read_counter,MfUltralightError,"Nfc*, uint8_t, MfUltralightCounter*"
|
||||||
Function,+,mf_ultralight_poller_sync_read_page,MfUltralightError,"Nfc*, uint16_t, MfUltralightPage*"
|
Function,+,mf_ultralight_poller_sync_read_page,MfUltralightError,"Nfc*, uint16_t, MfUltralightPage*"
|
||||||
Function,+,mf_ultralight_poller_sync_read_signature,MfUltralightError,"Nfc*, MfUltralightSignature*"
|
Function,+,mf_ultralight_poller_sync_read_signature,MfUltralightError,"Nfc*, MfUltralightSignature*"
|
||||||
|
|||||||
|
@@ -12,6 +12,7 @@ extern "C" {
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
FuriHalPwmOutputIdNone,
|
||||||
FuriHalPwmOutputIdTim1PA7,
|
FuriHalPwmOutputIdTim1PA7,
|
||||||
FuriHalPwmOutputIdLptim2PA4,
|
FuriHalPwmOutputIdLptim2PA4,
|
||||||
} FuriHalPwmOutputId;
|
} FuriHalPwmOutputId;
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ const GpioPinRecord gpio_pins[] = {
|
|||||||
{.pin = &gpio_ext_pa7,
|
{.pin = &gpio_ext_pa7,
|
||||||
.name = "PA7",
|
.name = "PA7",
|
||||||
.channel = FuriHalAdcChannel12,
|
.channel = FuriHalAdcChannel12,
|
||||||
|
.pwm_output = FuriHalPwmOutputIdTim1PA7,
|
||||||
.number = 2,
|
.number = 2,
|
||||||
.debug = false},
|
.debug = false},
|
||||||
{.pin = &gpio_ext_pa6,
|
{.pin = &gpio_ext_pa6,
|
||||||
@@ -83,6 +84,7 @@ const GpioPinRecord gpio_pins[] = {
|
|||||||
{.pin = &gpio_ext_pa4,
|
{.pin = &gpio_ext_pa4,
|
||||||
.name = "PA4",
|
.name = "PA4",
|
||||||
.channel = FuriHalAdcChannel9,
|
.channel = FuriHalAdcChannel9,
|
||||||
|
.pwm_output = FuriHalPwmOutputIdLptim2PA4,
|
||||||
.number = 4,
|
.number = 4,
|
||||||
.debug = false},
|
.debug = false},
|
||||||
{.pin = &gpio_ext_pb3,
|
{.pin = &gpio_ext_pb3,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <furi.h>
|
#include <furi.h>
|
||||||
#include <furi_hal_adc.h>
|
#include <furi_hal_adc.h>
|
||||||
|
#include <furi_hal_pwm.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -40,6 +41,7 @@ typedef struct {
|
|||||||
const GpioPin* pin;
|
const GpioPin* pin;
|
||||||
const char* name;
|
const char* name;
|
||||||
const FuriHalAdcChannel channel;
|
const FuriHalAdcChannel channel;
|
||||||
|
const FuriHalPwmOutputId pwm_output;
|
||||||
const uint8_t number;
|
const uint8_t number;
|
||||||
const bool debug;
|
const bool debug;
|
||||||
} GpioPinRecord;
|
} GpioPinRecord;
|
||||||
|
|||||||
Reference in New Issue
Block a user