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:
@@ -18,7 +18,7 @@ struct BtTestParam {
|
|||||||
void* context;
|
void* context;
|
||||||
};
|
};
|
||||||
|
|
||||||
ARRAY_DEF(BtTestParamArray, BtTestParam, M_POD_OPLIST);
|
ARRAY_DEF(BtTestParamArray, BtTestParam, M_POD_OPLIST); //-V658
|
||||||
|
|
||||||
struct BtTest {
|
struct BtTest {
|
||||||
View* view;
|
View* view;
|
||||||
|
|||||||
@@ -4,10 +4,23 @@
|
|||||||
|
|
||||||
#include <bt/bt_service/bt_keys_storage.h>
|
#include <bt/bt_service/bt_keys_storage.h>
|
||||||
#include <storage/storage.h>
|
#include <storage/storage.h>
|
||||||
|
#include <toolbox/saved_struct.h>
|
||||||
|
|
||||||
#define BT_TEST_KEY_STORAGE_FILE_PATH EXT_PATH("unit_tests/bt_test.keys")
|
#define BT_TEST_KEY_STORAGE_FILE_PATH EXT_PATH("unit_tests/bt_test.keys")
|
||||||
|
#define BT_TEST_MIGRATION_FILE_PATH EXT_PATH("unit_tests/bt_migration_test.keys")
|
||||||
#define BT_TEST_NVM_RAM_BUFF_SIZE (507 * 4) // The same as in ble NVM storage
|
#define BT_TEST_NVM_RAM_BUFF_SIZE (507 * 4) // The same as in ble NVM storage
|
||||||
|
|
||||||
|
// Identity root key
|
||||||
|
static const uint8_t gap_legacy_irk[16] =
|
||||||
|
{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0};
|
||||||
|
// Encryption root key
|
||||||
|
static const uint8_t gap_legacy_erk[16] =
|
||||||
|
{0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21};
|
||||||
|
|
||||||
|
// Test constants for migration (matching bt_keys_storage.c)
|
||||||
|
#define BT_KEYS_STORAGE_MAGIC_TEST (0x18)
|
||||||
|
#define BT_KEYS_STORAGE_VERSION_1_TEST (1)
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
Storage* storage;
|
Storage* storage;
|
||||||
BtKeysStorage* bt_keys_storage;
|
BtKeysStorage* bt_keys_storage;
|
||||||
@@ -88,6 +101,134 @@ static void bt_test_keys_remove_test_file(void) {
|
|||||||
"Can't remove test file");
|
"Can't remove test file");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to create a version 0 file manually
|
||||||
|
static bool
|
||||||
|
bt_test_create_v0_file(const char* file_path, const uint8_t* nvm_data, size_t nvm_size) {
|
||||||
|
// Version 0 files use saved_struct format with magic 0x18, version 0, containing only BLE pairing data
|
||||||
|
return saved_struct_save(
|
||||||
|
file_path,
|
||||||
|
nvm_data,
|
||||||
|
nvm_size,
|
||||||
|
BT_KEYS_STORAGE_MAGIC_TEST,
|
||||||
|
0); // Version 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to verify file format version
|
||||||
|
static bool bt_test_verify_file_version(const char* file_path, uint32_t expected_version) {
|
||||||
|
uint8_t magic, version;
|
||||||
|
size_t size;
|
||||||
|
|
||||||
|
if(!saved_struct_get_metadata(file_path, &magic, &version, &size)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (magic == BT_KEYS_STORAGE_MAGIC_TEST && version == expected_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test migration from version 0 to version 1, including root key preservation
|
||||||
|
static void bt_test_migration_v0_to_v1(void) {
|
||||||
|
// Create test NVM data
|
||||||
|
const size_t test_nvm_size = 100;
|
||||||
|
uint8_t test_nvm_data[test_nvm_size];
|
||||||
|
for(size_t i = 0; i < test_nvm_size; i++) {
|
||||||
|
test_nvm_data[i] = (uint8_t)(i & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a version 0 file
|
||||||
|
mu_assert(
|
||||||
|
bt_test_create_v0_file(BT_TEST_MIGRATION_FILE_PATH, test_nvm_data, test_nvm_size),
|
||||||
|
"Failed to create version 0 test file");
|
||||||
|
|
||||||
|
// Create BT keys storage and load the v0 file (should trigger migration)
|
||||||
|
BtKeysStorage* migration_storage = bt_keys_storage_alloc(BT_TEST_MIGRATION_FILE_PATH);
|
||||||
|
uint8_t loaded_buffer[BT_TEST_NVM_RAM_BUFF_SIZE];
|
||||||
|
memset(loaded_buffer, 0, sizeof(loaded_buffer));
|
||||||
|
bt_keys_storage_set_ram_params(migration_storage, loaded_buffer, sizeof(loaded_buffer));
|
||||||
|
|
||||||
|
// Load should succeed and migrate v0 to v1
|
||||||
|
mu_assert(bt_keys_storage_load(migration_storage), "Failed to load and migrate v0 file");
|
||||||
|
|
||||||
|
// Verify the file is now version 1
|
||||||
|
mu_assert(
|
||||||
|
bt_test_verify_file_version(BT_TEST_MIGRATION_FILE_PATH, BT_KEYS_STORAGE_VERSION_1_TEST),
|
||||||
|
"File was not migrated to version 1");
|
||||||
|
|
||||||
|
// Verify the NVM data was preserved during migration
|
||||||
|
mu_assert(
|
||||||
|
memcmp(test_nvm_data, loaded_buffer, test_nvm_size) == 0,
|
||||||
|
"NVM data was corrupted during migration");
|
||||||
|
|
||||||
|
// Verify that legacy root keys are used after migration
|
||||||
|
const GapRootSecurityKeys* migrated_keys = bt_keys_storage_get_root_keys(migration_storage);
|
||||||
|
mu_assert(
|
||||||
|
memcmp(migrated_keys->irk, gap_legacy_irk, sizeof(gap_legacy_irk)) == 0,
|
||||||
|
"IRK not set to legacy after migration");
|
||||||
|
mu_assert(
|
||||||
|
memcmp(migrated_keys->erk, gap_legacy_erk, sizeof(gap_legacy_erk)) == 0,
|
||||||
|
"ERK not set to legacy after migration");
|
||||||
|
|
||||||
|
bt_keys_storage_free(migration_storage);
|
||||||
|
storage_simply_remove(bt_test->storage, BT_TEST_MIGRATION_FILE_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that migration preserves existing pairing data and root keys are not changed on reload
|
||||||
|
static void bt_test_migration_preserves_pairings_and_keys(void) {
|
||||||
|
const size_t pairing_data_size = 200;
|
||||||
|
uint8_t pairing_data[pairing_data_size];
|
||||||
|
for(size_t i = 0; i < pairing_data_size; i++) {
|
||||||
|
pairing_data[i] = (uint8_t)((i * 7 + 42) & 0xFF);
|
||||||
|
}
|
||||||
|
mu_assert(
|
||||||
|
bt_test_create_v0_file(BT_TEST_MIGRATION_FILE_PATH, pairing_data, pairing_data_size),
|
||||||
|
"Failed to create v0 file with pairing data");
|
||||||
|
|
||||||
|
GapRootSecurityKeys keys_after_first_load;
|
||||||
|
for(int iteration = 0; iteration < 2; iteration++) {
|
||||||
|
BtKeysStorage* storage = bt_keys_storage_alloc(BT_TEST_MIGRATION_FILE_PATH);
|
||||||
|
uint8_t buffer[BT_TEST_NVM_RAM_BUFF_SIZE];
|
||||||
|
memset(buffer, 0, sizeof(buffer));
|
||||||
|
bt_keys_storage_set_ram_params(storage, buffer, sizeof(buffer));
|
||||||
|
mu_assert(bt_keys_storage_load(storage), "Failed to load on iteration");
|
||||||
|
mu_assert(
|
||||||
|
memcmp(pairing_data, buffer, pairing_data_size) == 0,
|
||||||
|
"Pairing data corrupted on iteration");
|
||||||
|
const GapRootSecurityKeys* keys = bt_keys_storage_get_root_keys(storage);
|
||||||
|
if(iteration == 0)
|
||||||
|
memcpy(&keys_after_first_load, keys, sizeof(GapRootSecurityKeys));
|
||||||
|
else
|
||||||
|
mu_assert(
|
||||||
|
memcmp(&keys_after_first_load, keys, sizeof(GapRootSecurityKeys)) == 0,
|
||||||
|
"Root keys changed after reload");
|
||||||
|
bt_keys_storage_free(storage);
|
||||||
|
}
|
||||||
|
storage_simply_remove(bt_test->storage, BT_TEST_MIGRATION_FILE_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that delete operation generates new secure keys in v1 and does not match legacy
|
||||||
|
static void bt_test_delete_generates_new_keys_and_not_legacy(void) {
|
||||||
|
BtKeysStorage* storage = bt_keys_storage_alloc(BT_TEST_MIGRATION_FILE_PATH);
|
||||||
|
uint8_t buffer[BT_TEST_NVM_RAM_BUFF_SIZE];
|
||||||
|
memset(buffer, 0x55, sizeof(buffer));
|
||||||
|
bt_keys_storage_set_ram_params(storage, buffer, sizeof(buffer));
|
||||||
|
mu_assert(bt_keys_storage_update(storage, buffer, 100), "Failed to create initial v1 file");
|
||||||
|
const GapRootSecurityKeys* original_keys = bt_keys_storage_get_root_keys(storage);
|
||||||
|
uint8_t original_keys_copy[sizeof(GapRootSecurityKeys)];
|
||||||
|
memcpy(original_keys_copy, original_keys, sizeof(original_keys_copy));
|
||||||
|
bt_keys_storage_delete(storage);
|
||||||
|
const GapRootSecurityKeys* new_keys = bt_keys_storage_get_root_keys(storage);
|
||||||
|
mu_assert(
|
||||||
|
memcmp(original_keys_copy, new_keys, sizeof(original_keys_copy)) != 0,
|
||||||
|
"Root keys were not regenerated after delete");
|
||||||
|
mu_assert(
|
||||||
|
memcmp(new_keys->irk, gap_legacy_irk, sizeof(gap_legacy_irk)) != 0,
|
||||||
|
"IRK after delete should not match legacy");
|
||||||
|
mu_assert(
|
||||||
|
memcmp(new_keys->erk, gap_legacy_erk, sizeof(gap_legacy_erk)) != 0,
|
||||||
|
"ERK after delete should not match legacy");
|
||||||
|
bt_keys_storage_free(storage);
|
||||||
|
storage_simply_remove(bt_test->storage, BT_TEST_MIGRATION_FILE_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
MU_TEST(bt_test_keys_storage_serial_profile) {
|
MU_TEST(bt_test_keys_storage_serial_profile) {
|
||||||
furi_check(bt_test);
|
furi_check(bt_test);
|
||||||
|
|
||||||
@@ -96,10 +237,28 @@ MU_TEST(bt_test_keys_storage_serial_profile) {
|
|||||||
bt_test_keys_remove_test_file();
|
bt_test_keys_remove_test_file();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MU_TEST(bt_test_migration_v0_to_v1_test) {
|
||||||
|
furi_check(bt_test);
|
||||||
|
bt_test_migration_v0_to_v1();
|
||||||
|
}
|
||||||
|
|
||||||
|
MU_TEST(bt_test_migration_preserves_pairings_and_keys_test) {
|
||||||
|
furi_check(bt_test);
|
||||||
|
bt_test_migration_preserves_pairings_and_keys();
|
||||||
|
}
|
||||||
|
|
||||||
|
MU_TEST(bt_test_delete_generates_new_keys_and_not_legacy_test) {
|
||||||
|
furi_check(bt_test);
|
||||||
|
bt_test_delete_generates_new_keys_and_not_legacy();
|
||||||
|
}
|
||||||
|
|
||||||
MU_TEST_SUITE(test_bt) {
|
MU_TEST_SUITE(test_bt) {
|
||||||
bt_test_alloc();
|
bt_test_alloc();
|
||||||
|
|
||||||
MU_RUN_TEST(bt_test_keys_storage_serial_profile);
|
MU_RUN_TEST(bt_test_keys_storage_serial_profile);
|
||||||
|
MU_RUN_TEST(bt_test_migration_v0_to_v1_test);
|
||||||
|
MU_RUN_TEST(bt_test_migration_preserves_pairings_and_keys_test);
|
||||||
|
MU_RUN_TEST(bt_test_delete_generates_new_keys_and_not_legacy_test);
|
||||||
|
|
||||||
bt_test_free();
|
bt_test_free();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#include "infrared_signal.h"
|
#include "infrared_signal.h"
|
||||||
|
|
||||||
ARRAY_DEF(SignalPositionArray, size_t, M_DEFAULT_OPLIST);
|
ARRAY_DEF(SignalPositionArray, size_t, M_DEFAULT_OPLIST); //-V658
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
size_t index;
|
size_t index;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ typedef struct {
|
|||||||
uint32_t ar1;
|
uint32_t ar1;
|
||||||
} Mfkey32LoggerParams;
|
} Mfkey32LoggerParams;
|
||||||
|
|
||||||
ARRAY_DEF(Mfkey32LoggerParams, Mfkey32LoggerParams, M_POD_OPLIST);
|
ARRAY_DEF(Mfkey32LoggerParams, Mfkey32LoggerParams, M_POD_OPLIST); //-V658
|
||||||
|
|
||||||
struct Mfkey32Logger {
|
struct Mfkey32Logger {
|
||||||
uint32_t cuid;
|
uint32_t cuid;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ typedef struct {
|
|||||||
NfcSupportedCardsPluginFeature feature;
|
NfcSupportedCardsPluginFeature feature;
|
||||||
} NfcSupportedCardsPluginCache;
|
} NfcSupportedCardsPluginCache;
|
||||||
|
|
||||||
ARRAY_DEF(NfcSupportedCardsPluginCache, NfcSupportedCardsPluginCache, M_POD_OPLIST);
|
ARRAY_DEF(NfcSupportedCardsPluginCache, NfcSupportedCardsPluginCache, M_POD_OPLIST); //-V658
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
NfcSupportedCardsLoadStateIdle,
|
NfcSupportedCardsLoadStateIdle,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ static void onewire_cli_search(PipeSide* pipe) {
|
|||||||
onewire_host_start(onewire);
|
onewire_host_start(onewire);
|
||||||
power_enable_otg(power, true);
|
power_enable_otg(power, true);
|
||||||
|
|
||||||
while(!done) {
|
while(!done) { //-V1044
|
||||||
if(onewire_host_search(onewire, address, OneWireHostSearchModeNormal) != 1) {
|
if(onewire_host_search(onewire, address, OneWireHostSearchModeNormal) != 1) {
|
||||||
printf("Search finished\r\n");
|
printf("Search finished\r\n");
|
||||||
onewire_host_reset_search(onewire);
|
onewire_host_reset_search(onewire);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ typedef struct {
|
|||||||
DateTime datetime;
|
DateTime datetime;
|
||||||
} SubGhzHistoryItem;
|
} SubGhzHistoryItem;
|
||||||
|
|
||||||
ARRAY_DEF(SubGhzHistoryItemArray, SubGhzHistoryItem, M_POD_OPLIST)
|
ARRAY_DEF(SubGhzHistoryItemArray, SubGhzHistoryItem, M_POD_OPLIST) //-V658
|
||||||
|
|
||||||
#define M_OPL_SubGhzHistoryItemArray_t() ARRAY_OPLIST(SubGhzHistoryItemArray, M_POD_OPLIST)
|
#define M_OPL_SubGhzHistoryItemArray_t() ARRAY_OPLIST(SubGhzHistoryItemArray, M_POD_OPLIST)
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ typedef struct {
|
|||||||
uint8_t type;
|
uint8_t type;
|
||||||
} SubGhzReceiverMenuItem;
|
} SubGhzReceiverMenuItem;
|
||||||
|
|
||||||
ARRAY_DEF(SubGhzReceiverMenuItemArray, SubGhzReceiverMenuItem, M_POD_OPLIST)
|
ARRAY_DEF(SubGhzReceiverMenuItemArray, SubGhzReceiverMenuItem, M_POD_OPLIST) //-V658
|
||||||
|
|
||||||
#define M_OPL_SubGhzReceiverMenuItemArray_t() \
|
#define M_OPL_SubGhzReceiverMenuItemArray_t() \
|
||||||
ARRAY_OPLIST(SubGhzReceiverMenuItemArray, M_POD_OPLIST)
|
ARRAY_OPLIST(SubGhzReceiverMenuItemArray, M_POD_OPLIST)
|
||||||
|
|||||||
@@ -418,6 +418,7 @@ static void bt_change_profile(Bt* bt, BtMessage* message) {
|
|||||||
bt->current_profile = furi_hal_bt_change_app(
|
bt->current_profile = furi_hal_bt_change_app(
|
||||||
message->data.profile.template,
|
message->data.profile.template,
|
||||||
message->data.profile.params,
|
message->data.profile.params,
|
||||||
|
bt_keys_storage_get_root_keys(bt->keys_storage),
|
||||||
bt_on_gap_event_callback,
|
bt_on_gap_event_callback,
|
||||||
bt);
|
bt);
|
||||||
if(bt->current_profile) {
|
if(bt->current_profile) {
|
||||||
@@ -473,7 +474,6 @@ static void bt_load_keys(Bt* bt) {
|
|||||||
bt_keys_storage_load(bt->keys_storage);
|
bt_keys_storage_load(bt->keys_storage);
|
||||||
|
|
||||||
bt->current_profile = NULL;
|
bt->current_profile = NULL;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
FURI_LOG_I(TAG, "Keys unchanged");
|
FURI_LOG_I(TAG, "Keys unchanged");
|
||||||
}
|
}
|
||||||
@@ -481,8 +481,12 @@ static void bt_load_keys(Bt* bt) {
|
|||||||
|
|
||||||
static void bt_start_application(Bt* bt) {
|
static void bt_start_application(Bt* bt) {
|
||||||
if(!bt->current_profile) {
|
if(!bt->current_profile) {
|
||||||
bt->current_profile =
|
bt->current_profile = furi_hal_bt_change_app(
|
||||||
furi_hal_bt_change_app(ble_profile_serial, NULL, bt_on_gap_event_callback, bt);
|
ble_profile_serial,
|
||||||
|
NULL,
|
||||||
|
bt_keys_storage_get_root_keys(bt->keys_storage),
|
||||||
|
bt_on_gap_event_callback,
|
||||||
|
bt);
|
||||||
|
|
||||||
if(!bt->current_profile) {
|
if(!bt->current_profile) {
|
||||||
FURI_LOG_E(TAG, "BLE App start failed");
|
FURI_LOG_E(TAG, "BLE App start failed");
|
||||||
|
|||||||
@@ -2,21 +2,92 @@
|
|||||||
|
|
||||||
#include <furi.h>
|
#include <furi.h>
|
||||||
#include <furi_hal_bt.h>
|
#include <furi_hal_bt.h>
|
||||||
#include <lib/toolbox/saved_struct.h>
|
#include <furi_hal_random.h>
|
||||||
#include <storage/storage.h>
|
|
||||||
|
#include <gap.h>
|
||||||
|
|
||||||
|
#include <storage/storage.h>
|
||||||
|
#include <toolbox/saved_struct.h>
|
||||||
|
|
||||||
#define BT_KEYS_STORAGE_VERSION (0)
|
|
||||||
#define BT_KEYS_STORAGE_MAGIC (0x18)
|
#define BT_KEYS_STORAGE_MAGIC (0x18)
|
||||||
|
#define BT_KEYS_STORAGE_VERSION (1)
|
||||||
|
#define BT_KEYS_STORAGE_LEGACY_VERSION (0) // Legacy version with no root keys
|
||||||
|
|
||||||
#define TAG "BtKeyStorage"
|
#define TAG "BtKeyStorage"
|
||||||
|
|
||||||
|
// Identity root key
|
||||||
|
static const uint8_t gap_legacy_irk[16] =
|
||||||
|
{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0};
|
||||||
|
// Encryption root key
|
||||||
|
static const uint8_t gap_legacy_erk[16] =
|
||||||
|
{0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21};
|
||||||
|
|
||||||
struct BtKeysStorage {
|
struct BtKeysStorage {
|
||||||
uint8_t* nvm_sram_buff;
|
uint8_t* nvm_sram_buff;
|
||||||
uint16_t nvm_sram_buff_size;
|
uint16_t nvm_sram_buff_size;
|
||||||
uint16_t current_size;
|
uint16_t current_size;
|
||||||
FuriString* file_path;
|
FuriString* file_path;
|
||||||
|
GapRootSecurityKeys root_keys;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
GapRootSecurityKeys root_keys;
|
||||||
|
uint8_t pairing_data[];
|
||||||
|
} BtKeysStorageFile;
|
||||||
|
|
||||||
|
static bool bt_keys_storage_save(BtKeysStorage* instance) {
|
||||||
|
furi_assert(instance);
|
||||||
|
|
||||||
|
size_t total_size = sizeof(BtKeysStorageFile) + instance->current_size;
|
||||||
|
BtKeysStorageFile* save_data = malloc(total_size);
|
||||||
|
|
||||||
|
memcpy(&save_data->root_keys, &instance->root_keys, sizeof(GapRootSecurityKeys));
|
||||||
|
|
||||||
|
furi_hal_bt_nvm_sram_sem_acquire();
|
||||||
|
memcpy(save_data->pairing_data, instance->nvm_sram_buff, instance->current_size);
|
||||||
|
furi_hal_bt_nvm_sram_sem_release();
|
||||||
|
|
||||||
|
bool saved = saved_struct_save(
|
||||||
|
furi_string_get_cstr(instance->file_path),
|
||||||
|
save_data,
|
||||||
|
sizeof(GapRootSecurityKeys) + instance->current_size,
|
||||||
|
BT_KEYS_STORAGE_MAGIC,
|
||||||
|
BT_KEYS_STORAGE_VERSION);
|
||||||
|
|
||||||
|
free(save_data);
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bt_keys_storage_load_keys_and_pairings(
|
||||||
|
BtKeysStorage* instance,
|
||||||
|
const uint8_t* file_data,
|
||||||
|
size_t data_size) {
|
||||||
|
if(data_size < sizeof(GapRootSecurityKeys)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BtKeysStorageFile* loaded = (const BtKeysStorageFile*)file_data;
|
||||||
|
memcpy(&instance->root_keys, &loaded->root_keys, sizeof(GapRootSecurityKeys));
|
||||||
|
|
||||||
|
size_t ble_data_size = data_size - sizeof(GapRootSecurityKeys);
|
||||||
|
if(ble_data_size > instance->nvm_sram_buff_size) {
|
||||||
|
FURI_LOG_E(TAG, "BLE data too large for SRAM buffer");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_hal_bt_nvm_sram_sem_acquire();
|
||||||
|
memcpy(instance->nvm_sram_buff, loaded->pairing_data, ble_data_size);
|
||||||
|
instance->current_size = ble_data_size;
|
||||||
|
furi_hal_bt_nvm_sram_sem_release();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bt_keys_storage_regenerate_root_keys(BtKeysStorage* instance) {
|
||||||
|
furi_hal_random_fill_buf(instance->root_keys.erk, sizeof(instance->root_keys.erk));
|
||||||
|
furi_hal_random_fill_buf(instance->root_keys.irk, sizeof(instance->root_keys.irk));
|
||||||
|
}
|
||||||
|
|
||||||
bool bt_keys_storage_delete(BtKeysStorage* instance) {
|
bool bt_keys_storage_delete(BtKeysStorage* instance) {
|
||||||
furi_assert(instance);
|
furi_assert(instance);
|
||||||
|
|
||||||
@@ -25,6 +96,15 @@ bool bt_keys_storage_delete(BtKeysStorage* instance) {
|
|||||||
|
|
||||||
furi_hal_bt_stop_advertising();
|
furi_hal_bt_stop_advertising();
|
||||||
delete_succeed = furi_hal_bt_clear_white_list();
|
delete_succeed = furi_hal_bt_clear_white_list();
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "Root keys regen");
|
||||||
|
bt_keys_storage_regenerate_root_keys(instance);
|
||||||
|
|
||||||
|
instance->current_size = 0;
|
||||||
|
if(!bt_keys_storage_save(instance)) {
|
||||||
|
FURI_LOG_E(TAG, "Save after delete failed");
|
||||||
|
}
|
||||||
|
|
||||||
if(bt_is_active) {
|
if(bt_is_active) {
|
||||||
furi_hal_bt_start_advertising();
|
furi_hal_bt_start_advertising();
|
||||||
}
|
}
|
||||||
@@ -42,6 +122,7 @@ BtKeysStorage* bt_keys_storage_alloc(const char* keys_storage_path) {
|
|||||||
instance->file_path = furi_string_alloc();
|
instance->file_path = furi_string_alloc();
|
||||||
furi_string_set_str(instance->file_path, keys_storage_path);
|
furi_string_set_str(instance->file_path, keys_storage_path);
|
||||||
|
|
||||||
|
bt_keys_storage_regenerate_root_keys(instance);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,20 +157,31 @@ static bool bt_keys_storage_file_exists(const char* file_path) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool bt_keys_storage_validate_file(const char* file_path, size_t* payload_size) {
|
static bool bt_keys_storage_validate_file(
|
||||||
|
const char* file_path,
|
||||||
|
size_t* payload_size,
|
||||||
|
uint8_t* file_version) {
|
||||||
uint8_t magic, version;
|
uint8_t magic, version;
|
||||||
size_t size;
|
size_t size;
|
||||||
|
|
||||||
if(!saved_struct_get_metadata(file_path, &magic, &version, &size)) {
|
if(!saved_struct_get_metadata(file_path, &magic, &version, &size)) {
|
||||||
FURI_LOG_E(TAG, "Failed to get metadata");
|
FURI_LOG_W(TAG, "Failed to get metadata");
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
} else if(magic != BT_KEYS_STORAGE_MAGIC || version != BT_KEYS_STORAGE_VERSION) {
|
} else if(magic != BT_KEYS_STORAGE_MAGIC) {
|
||||||
FURI_LOG_E(TAG, "File version mismatch");
|
FURI_LOG_W(TAG, "File magic mismatch");
|
||||||
|
return false;
|
||||||
|
} else if(version > BT_KEYS_STORAGE_VERSION) {
|
||||||
|
FURI_LOG_E(
|
||||||
|
TAG,
|
||||||
|
"File version %d is newer than supported version %d",
|
||||||
|
version,
|
||||||
|
BT_KEYS_STORAGE_VERSION);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*payload_size = size;
|
*payload_size = size;
|
||||||
|
*file_version = version;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,32 +194,45 @@ bool bt_keys_storage_is_changed(BtKeysStorage* instance) {
|
|||||||
do {
|
do {
|
||||||
const char* file_path = furi_string_get_cstr(instance->file_path);
|
const char* file_path = furi_string_get_cstr(instance->file_path);
|
||||||
size_t payload_size;
|
size_t payload_size;
|
||||||
|
uint8_t file_version;
|
||||||
|
|
||||||
if(!bt_keys_storage_file_exists(file_path)) {
|
if(!bt_keys_storage_file_exists(file_path)) {
|
||||||
FURI_LOG_W(TAG, "Missing or empty file");
|
FURI_LOG_W(TAG, "Missing or empty file");
|
||||||
|
is_changed = true;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
} else if(!bt_keys_storage_validate_file(file_path, &payload_size)) {
|
if(!bt_keys_storage_validate_file(file_path, &payload_size, &file_version)) {
|
||||||
FURI_LOG_E(TAG, "Invalid or corrupted file");
|
FURI_LOG_W(TAG, "Invalid or corrupted file");
|
||||||
|
is_changed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Early check for legacy version: always considered changed, no need to load
|
||||||
|
if(file_version == BT_KEYS_STORAGE_LEGACY_VERSION) {
|
||||||
|
is_changed = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
data_buffer = malloc(payload_size);
|
data_buffer = malloc(payload_size);
|
||||||
|
|
||||||
const bool data_loaded = saved_struct_load(
|
const bool data_loaded = saved_struct_load(
|
||||||
file_path, data_buffer, payload_size, BT_KEYS_STORAGE_MAGIC, BT_KEYS_STORAGE_VERSION);
|
file_path, data_buffer, payload_size, BT_KEYS_STORAGE_MAGIC, file_version);
|
||||||
|
|
||||||
if(!data_loaded) {
|
if(!data_loaded) {
|
||||||
FURI_LOG_E(TAG, "Failed to load file");
|
FURI_LOG_E(TAG, "Failed to load file");
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
} else if(payload_size == instance->current_size) {
|
// At this point, it's version 1 file we have
|
||||||
|
const BtKeysStorageFile* loaded = (const BtKeysStorageFile*)data_buffer;
|
||||||
|
size_t expected_file_size = sizeof(GapRootSecurityKeys) + instance->current_size;
|
||||||
|
if(payload_size == expected_file_size) {
|
||||||
furi_hal_bt_nvm_sram_sem_acquire();
|
furi_hal_bt_nvm_sram_sem_acquire();
|
||||||
is_changed = memcmp(data_buffer, instance->nvm_sram_buff, payload_size);
|
is_changed =
|
||||||
|
memcmp(loaded->pairing_data, instance->nvm_sram_buff, instance->current_size);
|
||||||
furi_hal_bt_nvm_sram_sem_release();
|
furi_hal_bt_nvm_sram_sem_release();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
FURI_LOG_D(TAG, "Size mismatch");
|
FURI_LOG_D(TAG, "NVRAM sz mismatch (v1)");
|
||||||
is_changed = true;
|
is_changed = true;
|
||||||
}
|
}
|
||||||
} while(false);
|
} while(false);
|
||||||
@@ -139,45 +244,59 @@ bool bt_keys_storage_is_changed(BtKeysStorage* instance) {
|
|||||||
return is_changed;
|
return is_changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool bt_keys_storage_load_legacy_pairings(
|
||||||
|
BtKeysStorage* instance,
|
||||||
|
const uint8_t* file_data,
|
||||||
|
size_t payload_size) {
|
||||||
|
FURI_LOG_I(TAG, "Loaded v0, upgrading to v1");
|
||||||
|
memcpy(instance->root_keys.irk, gap_legacy_irk, sizeof(instance->root_keys.irk));
|
||||||
|
memcpy(instance->root_keys.erk, gap_legacy_erk, sizeof(instance->root_keys.erk));
|
||||||
|
if(payload_size > instance->nvm_sram_buff_size) {
|
||||||
|
FURI_LOG_E(TAG, "Pairing too large");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
furi_hal_bt_nvm_sram_sem_acquire();
|
||||||
|
memcpy(instance->nvm_sram_buff, file_data, payload_size);
|
||||||
|
instance->current_size = payload_size;
|
||||||
|
furi_hal_bt_nvm_sram_sem_release();
|
||||||
|
if(!bt_keys_storage_save(instance)) {
|
||||||
|
FURI_LOG_W(TAG, "Upgrade to v1 failed");
|
||||||
|
} else {
|
||||||
|
FURI_LOG_I(TAG, "Upgraded to v1");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool bt_keys_storage_load(BtKeysStorage* instance) {
|
bool bt_keys_storage_load(BtKeysStorage* instance) {
|
||||||
furi_assert(instance);
|
furi_assert(instance);
|
||||||
|
|
||||||
|
const char* file_path = furi_string_get_cstr(instance->file_path);
|
||||||
|
size_t payload_size;
|
||||||
|
uint8_t file_version;
|
||||||
|
if(!bt_keys_storage_validate_file(file_path, &payload_size, &file_version)) {
|
||||||
|
FURI_LOG_E(TAG, "Invalid or corrupted file");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool loaded = false;
|
bool loaded = false;
|
||||||
|
uint8_t* file_data = malloc(payload_size);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
const char* file_path = furi_string_get_cstr(instance->file_path);
|
if(!saved_struct_load(
|
||||||
|
file_path, file_data, payload_size, BT_KEYS_STORAGE_MAGIC, file_version)) {
|
||||||
// Get payload size
|
FURI_LOG_E(TAG, "Failed to load");
|
||||||
size_t payload_size;
|
|
||||||
if(!bt_keys_storage_validate_file(file_path, &payload_size)) {
|
|
||||||
FURI_LOG_E(TAG, "Invalid or corrupted file");
|
|
||||||
break;
|
|
||||||
|
|
||||||
} else if(payload_size > instance->nvm_sram_buff_size) {
|
|
||||||
FURI_LOG_E(TAG, "NVM RAM buffer overflow");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load saved data to ram
|
if(file_version == BT_KEYS_STORAGE_LEGACY_VERSION) {
|
||||||
furi_hal_bt_nvm_sram_sem_acquire();
|
loaded = bt_keys_storage_load_legacy_pairings(instance, file_data, payload_size);
|
||||||
const bool data_loaded = saved_struct_load(
|
|
||||||
file_path,
|
|
||||||
instance->nvm_sram_buff,
|
|
||||||
payload_size,
|
|
||||||
BT_KEYS_STORAGE_MAGIC,
|
|
||||||
BT_KEYS_STORAGE_VERSION);
|
|
||||||
furi_hal_bt_nvm_sram_sem_release();
|
|
||||||
|
|
||||||
if(!data_loaded) {
|
|
||||||
FURI_LOG_E(TAG, "Failed to load file");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// Only v1 left
|
||||||
instance->current_size = payload_size;
|
loaded = bt_keys_storage_load_keys_and_pairings(instance, file_data, payload_size);
|
||||||
|
|
||||||
loaded = true;
|
|
||||||
} while(false);
|
} while(false);
|
||||||
|
|
||||||
|
free(file_data);
|
||||||
return loaded;
|
return loaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,14 +322,8 @@ bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32
|
|||||||
|
|
||||||
instance->current_size = new_size;
|
instance->current_size = new_size;
|
||||||
|
|
||||||
furi_hal_bt_nvm_sram_sem_acquire();
|
// Save using version 1 format with embedded root keys
|
||||||
bool data_updated = saved_struct_save(
|
bool data_updated = bt_keys_storage_save(instance);
|
||||||
furi_string_get_cstr(instance->file_path),
|
|
||||||
instance->nvm_sram_buff,
|
|
||||||
new_size,
|
|
||||||
BT_KEYS_STORAGE_MAGIC,
|
|
||||||
BT_KEYS_STORAGE_VERSION);
|
|
||||||
furi_hal_bt_nvm_sram_sem_release();
|
|
||||||
|
|
||||||
if(!data_updated) {
|
if(!data_updated) {
|
||||||
FURI_LOG_E(TAG, "Failed to update key storage");
|
FURI_LOG_E(TAG, "Failed to update key storage");
|
||||||
@@ -222,3 +335,9 @@ bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32
|
|||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GapRootSecurityKeys* bt_keys_storage_get_root_keys(BtKeysStorage* instance) {
|
||||||
|
furi_assert(instance);
|
||||||
|
|
||||||
|
return &instance->root_keys;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <gap.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
@@ -19,6 +21,8 @@ void bt_keys_storage_set_ram_params(BtKeysStorage* instance, uint8_t* buff, uint
|
|||||||
|
|
||||||
bool bt_keys_storage_is_changed(BtKeysStorage* instance);
|
bool bt_keys_storage_is_changed(BtKeysStorage* instance);
|
||||||
|
|
||||||
|
const GapRootSecurityKeys* bt_keys_storage_get_root_keys(BtKeysStorage* instance);
|
||||||
|
|
||||||
bool bt_keys_storage_load(BtKeysStorage* instance);
|
bool bt_keys_storage_load(BtKeysStorage* instance);
|
||||||
|
|
||||||
bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size);
|
bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "bt_settings_filename.h"
|
#include "bt_settings_filename.h"
|
||||||
|
|
||||||
#include <furi.h>
|
#include <furi.h>
|
||||||
|
|
||||||
#include <storage/storage.h>
|
#include <storage/storage.h>
|
||||||
#include <toolbox/saved_struct.h>
|
#include <toolbox/saved_struct.h>
|
||||||
|
|
||||||
@@ -14,12 +15,13 @@
|
|||||||
void bt_settings_load(BtSettings* bt_settings) {
|
void bt_settings_load(BtSettings* bt_settings) {
|
||||||
furi_assert(bt_settings);
|
furi_assert(bt_settings);
|
||||||
|
|
||||||
const bool success = saved_struct_load(
|
const bool load_success = saved_struct_load(
|
||||||
BT_SETTINGS_PATH, bt_settings, sizeof(BtSettings), BT_SETTINGS_MAGIC, BT_SETTINGS_VERSION);
|
BT_SETTINGS_PATH, bt_settings, sizeof(BtSettings), BT_SETTINGS_MAGIC, BT_SETTINGS_VERSION);
|
||||||
|
|
||||||
if(!success) {
|
if(!load_success) {
|
||||||
FURI_LOG_W(TAG, "Failed to load settings, using defaults");
|
FURI_LOG_W(TAG, "Failed to load settings, using defaults");
|
||||||
memset(bt_settings, 0, sizeof(BtSettings));
|
|
||||||
|
bt_settings->enabled = false;
|
||||||
bt_settings_save(bt_settings);
|
bt_settings_save(bt_settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,12 +93,17 @@ static const FrameBubble*
|
|||||||
bubble_animation_pick_bubble(BubbleAnimationViewModel* model, bool active) {
|
bubble_animation_pick_bubble(BubbleAnimationViewModel* model, bool active) {
|
||||||
const FrameBubble* bubble = NULL;
|
const FrameBubble* bubble = NULL;
|
||||||
|
|
||||||
if((model->active_bubbles == 0) && (model->passive_bubbles == 0)) {
|
// Check for division by zero based on the active parameter
|
||||||
|
if((active && model->active_bubbles == 0) || (!active && model->passive_bubbles == 0)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t index =
|
uint8_t random_value = furi_hal_random_get();
|
||||||
furi_hal_random_get() % (active ? model->active_bubbles : model->passive_bubbles);
|
// In case random generator return zero lets set it to 3
|
||||||
|
if(random_value == 0) {
|
||||||
|
random_value = 3;
|
||||||
|
}
|
||||||
|
uint8_t index = random_value % (active ? model->active_bubbles : model->passive_bubbles);
|
||||||
const BubbleAnimation* animation = model->current;
|
const BubbleAnimation* animation = model->current;
|
||||||
|
|
||||||
for(int i = 0; i < animation->frame_bubble_sequences_count; ++i) {
|
for(int i = 0; i < animation->frame_bubble_sequences_count; ++i) {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ typedef struct {
|
|||||||
void* context;
|
void* context;
|
||||||
} CanvasCallbackPair;
|
} CanvasCallbackPair;
|
||||||
|
|
||||||
ARRAY_DEF(CanvasCallbackPairArray, CanvasCallbackPair, M_POD_OPLIST);
|
ARRAY_DEF(CanvasCallbackPairArray, CanvasCallbackPair, M_POD_OPLIST); //-V658
|
||||||
|
|
||||||
#define M_OPL_CanvasCallbackPairArray_t() ARRAY_OPLIST(CanvasCallbackPairArray, M_POD_OPLIST)
|
#define M_OPL_CanvasCallbackPairArray_t() ARRAY_OPLIST(CanvasCallbackPairArray, M_POD_OPLIST)
|
||||||
|
|
||||||
|
|||||||
@@ -912,7 +912,7 @@ void elements_text_box(
|
|||||||
for(size_t i = 0; i < line_num; i++) {
|
for(size_t i = 0; i < line_num; i++) {
|
||||||
for(size_t j = 0; j < line[i].len; j++) {
|
for(size_t j = 0; j < line[i].len; j++) {
|
||||||
// Process format symbols
|
// Process format symbols
|
||||||
if(line[i].text[j] == '\e' && j < line[i].len - 1) {
|
if(line[i].text[j] == '\e' && j < line[i].len - 1) { //-V781
|
||||||
++j;
|
++j;
|
||||||
if(line[i].text[j] == ELEMENTS_BOLD_MARKER) {
|
if(line[i].text[j] == ELEMENTS_BOLD_MARKER) {
|
||||||
if(bold) {
|
if(bold) {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ struct ButtonMenuItem {
|
|||||||
void* callback_context;
|
void* callback_context;
|
||||||
};
|
};
|
||||||
|
|
||||||
ARRAY_DEF(ButtonMenuItemArray, ButtonMenuItem, M_POD_OPLIST);
|
ARRAY_DEF(ButtonMenuItemArray, ButtonMenuItem, M_POD_OPLIST); //-V658
|
||||||
|
|
||||||
struct ButtonMenu {
|
struct ButtonMenu {
|
||||||
View* view;
|
View* view;
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ void button_panel_reserve(ButtonPanel* button_panel, size_t reserve_x, size_t re
|
|||||||
furi_check(reserve_x > 0);
|
furi_check(reserve_x > 0);
|
||||||
furi_check(reserve_y > 0);
|
furi_check(reserve_y > 0);
|
||||||
|
|
||||||
with_view_model(
|
with_view_model( //-V621
|
||||||
button_panel->view,
|
button_panel->view,
|
||||||
ButtonPanelModel * model,
|
ButtonPanelModel * model,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ typedef enum {
|
|||||||
(WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit | \
|
(WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit | \
|
||||||
WorkerEvtFolderRefresh | WorkerEvtConfigChange)
|
WorkerEvtFolderRefresh | WorkerEvtConfigChange)
|
||||||
|
|
||||||
ARRAY_DEF(IdxLastArray, int32_t)
|
ARRAY_DEF(IdxLastArray, int32_t) //-V658
|
||||||
ARRAY_DEF(ExtFilterArray, FuriString*, FURI_STRING_OPLIST)
|
ARRAY_DEF(ExtFilterArray, FuriString*, FURI_STRING_OPLIST) //-V658
|
||||||
|
|
||||||
struct BrowserWorker {
|
struct BrowserWorker {
|
||||||
FuriThread* thread;
|
FuriThread* thread;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ typedef struct {
|
|||||||
void* callback_context;
|
void* callback_context;
|
||||||
} MenuItem;
|
} MenuItem;
|
||||||
|
|
||||||
ARRAY_DEF(MenuItemArray, MenuItem, M_POD_OPLIST);
|
ARRAY_DEF(MenuItemArray, MenuItem, M_POD_OPLIST); //-V658
|
||||||
|
|
||||||
#define M_OPL_MenuItemArray_t() ARRAY_OPLIST(MenuItemArray, M_POD_OPLIST)
|
#define M_OPL_MenuItemArray_t() ARRAY_OPLIST(MenuItemArray, M_POD_OPLIST)
|
||||||
|
|
||||||
|
|||||||
@@ -93,13 +93,12 @@ static bool popup_view_input_callback(InputEvent* event, void* context) {
|
|||||||
void popup_start_timer(void* context) {
|
void popup_start_timer(void* context) {
|
||||||
Popup* popup = context;
|
Popup* popup = context;
|
||||||
if(popup->timer_enabled) {
|
if(popup->timer_enabled) {
|
||||||
uint32_t timer_period =
|
uint32_t timer_period = furi_ms_to_ticks(popup->timer_period_in_ms);
|
||||||
popup->timer_period_in_ms / (1000.0f / furi_kernel_get_tick_frequency());
|
|
||||||
if(timer_period == 0) timer_period = 1;
|
if(timer_period == 0) timer_period = 1;
|
||||||
|
|
||||||
if(furi_timer_start(popup->timer, timer_period) != FuriStatusOk) {
|
if(furi_timer_start(popup->timer, timer_period) != FuriStatusOk) {
|
||||||
furi_crash();
|
furi_crash();
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ void popup_free(Popup* popup);
|
|||||||
*/
|
*/
|
||||||
View* popup_get_view(Popup* popup);
|
View* popup_get_view(Popup* popup);
|
||||||
|
|
||||||
/** Set popup callback function
|
/** Set popup timeout callback
|
||||||
*
|
*
|
||||||
* @param popup Popup instance
|
* @param popup Popup instance
|
||||||
* @param callback PopupCallback
|
* @param callback PopupCallback
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ struct VariableItem {
|
|||||||
void* context;
|
void* context;
|
||||||
};
|
};
|
||||||
|
|
||||||
ARRAY_DEF(VariableItemArray, VariableItem, M_POD_OPLIST);
|
ARRAY_DEF(VariableItemArray, VariableItem, M_POD_OPLIST); //-V658
|
||||||
|
|
||||||
struct VariableItemList {
|
struct VariableItemList {
|
||||||
View* view;
|
View* view;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ typedef struct {
|
|||||||
FuriString* text;
|
FuriString* text;
|
||||||
} TextScrollLineArray;
|
} TextScrollLineArray;
|
||||||
|
|
||||||
ARRAY_DEF(TextScrollLineArray, TextScrollLineArray, M_POD_OPLIST)
|
ARRAY_DEF(TextScrollLineArray, TextScrollLineArray, M_POD_OPLIST) //-V658
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
TextScrollLineArray_t line_array;
|
TextScrollLineArray_t line_array;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
#include "scene_manager.h"
|
#include "scene_manager.h"
|
||||||
#include <m-array.h>
|
#include <m-array.h>
|
||||||
|
|
||||||
ARRAY_DEF(SceneManagerIdStack, uint32_t, M_DEFAULT_OPLIST);
|
ARRAY_DEF(SceneManagerIdStack, uint32_t, M_DEFAULT_OPLIST); //-V658
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t state;
|
uint32_t state;
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ static void loader_show_gui_error(
|
|||||||
LoaderMessageLoaderStatusResult status,
|
LoaderMessageLoaderStatusResult status,
|
||||||
const char* name,
|
const char* name,
|
||||||
FuriString* error_message) {
|
FuriString* error_message) {
|
||||||
|
furi_check(name);
|
||||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||||
DialogMessage* message = dialog_message_alloc();
|
DialogMessage* message = dialog_message_alloc();
|
||||||
|
|
||||||
@@ -654,6 +655,8 @@ static LoaderMessageLoaderStatusResult loader_do_start_by_name(
|
|||||||
status.value = loader_make_success_status(error_message);
|
status.value = loader_make_success_status(error_message);
|
||||||
status.error = LoaderStatusErrorUnknown;
|
status.error = LoaderStatusErrorUnknown;
|
||||||
|
|
||||||
|
if(name == NULL) return status;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// check lock
|
// check lock
|
||||||
if(loader_do_is_locked(loader)) {
|
if(loader_do_is_locked(loader)) {
|
||||||
@@ -885,7 +888,10 @@ int32_t loader_srv(void* p) {
|
|||||||
switch(message.type) {
|
switch(message.type) {
|
||||||
case LoaderMessageTypeStartByName: {
|
case LoaderMessageTypeStartByName: {
|
||||||
LoaderMessageLoaderStatusResult status = loader_do_start_by_name(
|
LoaderMessageLoaderStatusResult status = loader_do_start_by_name(
|
||||||
loader, message.start.name, message.start.args, message.start.error_message);
|
loader,
|
||||||
|
message.start.name,
|
||||||
|
message.start.args,
|
||||||
|
message.start.error_message); //-V595
|
||||||
*(message.status_value) = status;
|
*(message.status_value) = status;
|
||||||
if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader);
|
if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader);
|
||||||
api_lock_unlock(message.api_lock);
|
api_lock_unlock(message.api_lock);
|
||||||
@@ -894,7 +900,7 @@ int32_t loader_srv(void* p) {
|
|||||||
case LoaderMessageTypeStartByNameDetachedWithGuiError: {
|
case LoaderMessageTypeStartByNameDetachedWithGuiError: {
|
||||||
FuriString* error_message = furi_string_alloc();
|
FuriString* error_message = furi_string_alloc();
|
||||||
LoaderMessageLoaderStatusResult status = loader_do_start_by_name(
|
LoaderMessageLoaderStatusResult status = loader_do_start_by_name(
|
||||||
loader, message.start.name, message.start.args, error_message);
|
loader, message.start.name, message.start.args, error_message); //-V595
|
||||||
loader_show_gui_error(status, message.start.name, error_message);
|
loader_show_gui_error(status, message.start.name, error_message);
|
||||||
if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader);
|
if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader);
|
||||||
if(message.start.name) free((void*)message.start.name);
|
if(message.start.name) free((void*)message.start.name);
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex
|
|||||||
finish = true;
|
finish = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
while(!finish) {
|
while(!finish) { //-V1044
|
||||||
FileInfo fileinfo;
|
FileInfo fileinfo;
|
||||||
char* name = malloc(MAX_NAME_LENGTH + 1);
|
char* name = malloc(MAX_NAME_LENGTH + 1);
|
||||||
if(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) {
|
if(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) {
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ static void rpc_system_system_ping_process(const PB_Main* request, void* context
|
|||||||
}
|
}
|
||||||
|
|
||||||
PB_Main response = PB_Main_init_default;
|
PB_Main response = PB_Main_init_default;
|
||||||
response.command_status = PB_CommandStatus_OK;
|
// PB_CommandStatus_OK is 0 in current case, and var by default is 0 too, commenting PVS warn
|
||||||
|
response.command_status = PB_CommandStatus_OK; //-V1048
|
||||||
response.command_id = request->command_id;
|
response.command_id = request->command_id;
|
||||||
response.which_content = PB_Main_system_ping_response_tag;
|
response.which_content = PB_Main_system_ping_response_tag;
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ void desktop_settings_scene_favorite_on_enter(void* context) {
|
|||||||
default_passport = true;
|
default_passport = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
favorite_id &= ~(SCENE_STATE_SET_DUMMY_APP);
|
favorite_id &= ~(SCENE_STATE_SET_DUMMY_APP); //-V784
|
||||||
furi_assert(favorite_id < DummyAppNumber);
|
furi_assert(favorite_id < DummyAppNumber);
|
||||||
curr_favorite_app = &app->settings.dummy_apps[favorite_id];
|
curr_favorite_app = &app->settings.dummy_apps[favorite_id];
|
||||||
is_dummy_app = true;
|
is_dummy_app = true;
|
||||||
@@ -160,7 +160,7 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e
|
|||||||
furi_assert(favorite_id < FavoriteAppNumber);
|
furi_assert(favorite_id < FavoriteAppNumber);
|
||||||
curr_favorite_app = &app->settings.favorite_apps[favorite_id];
|
curr_favorite_app = &app->settings.favorite_apps[favorite_id];
|
||||||
} else {
|
} else {
|
||||||
favorite_id &= ~(SCENE_STATE_SET_DUMMY_APP);
|
favorite_id &= ~(SCENE_STATE_SET_DUMMY_APP); //-V784
|
||||||
furi_assert(favorite_id < DummyAppNumber);
|
furi_assert(favorite_id < DummyAppNumber);
|
||||||
curr_favorite_app = &app->settings.dummy_apps[favorite_id];
|
curr_favorite_app = &app->settings.dummy_apps[favorite_id];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,54 @@ App(
|
|||||||
sources=["modules/js_gui/text_input.c"],
|
sources=["modules/js_gui/text_input.c"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="js_gui__number_input",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="js_view_number_input_ep",
|
||||||
|
requires=["js_app"],
|
||||||
|
sources=["modules/js_gui/number_input.c"],
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="js_gui__button_panel",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="js_view_button_panel_ep",
|
||||||
|
requires=["js_app"],
|
||||||
|
sources=["modules/js_gui/button_panel.c"],
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="js_gui__popup",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="js_view_popup_ep",
|
||||||
|
requires=["js_app"],
|
||||||
|
sources=["modules/js_gui/popup.c"],
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="js_gui__button_menu",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="js_view_button_menu_ep",
|
||||||
|
requires=["js_app"],
|
||||||
|
sources=["modules/js_gui/button_menu.c"],
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="js_gui__menu",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="js_view_menu_ep",
|
||||||
|
requires=["js_app"],
|
||||||
|
sources=["modules/js_gui/menu.c"],
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="js_gui__vi_list",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="js_view_vi_list_ep",
|
||||||
|
requires=["js_app"],
|
||||||
|
sources=["modules/js_gui/vi_list.c"],
|
||||||
|
)
|
||||||
|
|
||||||
App(
|
App(
|
||||||
appid="js_gui__byte_input",
|
appid="js_gui__byte_input",
|
||||||
apptype=FlipperAppType.PLUGIN,
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ 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 buttonMenuView = require("gui/button_menu");
|
||||||
|
let buttonPanelView = require("gui/button_panel");
|
||||||
|
let menuView = require("gui/menu");
|
||||||
|
let numberInputView = require("gui/number_input");
|
||||||
|
let popupView = require("gui/popup");
|
||||||
|
let viListView = require("gui/vi_list");
|
||||||
let widget = require("gui/widget");
|
let widget = require("gui/widget");
|
||||||
let icon = require("gui/icon");
|
let icon = require("gui/icon");
|
||||||
let flipper = require("flipper");
|
let flipper = require("flipper");
|
||||||
@@ -27,6 +33,11 @@ let stopwatchWidgetElements = [
|
|||||||
{ element: "button", button: "right", text: "Back" },
|
{ element: "button", button: "right", text: "Back" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// icons for the button panel
|
||||||
|
let offIcons = [icon.getBuiltin("off_19x20"), icon.getBuiltin("off_hover_19x20")];
|
||||||
|
let powerIcons = [icon.getBuiltin("power_19x20"), icon.getBuiltin("power_hover_19x20")];
|
||||||
|
let settingsIcon = icon.getBuiltin("Settings_14");
|
||||||
|
|
||||||
// declare view instances
|
// declare view instances
|
||||||
let views = {
|
let views = {
|
||||||
loading: loadingView.make(),
|
loading: loadingView.make(),
|
||||||
@@ -48,9 +59,42 @@ let views = {
|
|||||||
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),
|
stopwatchWidget: widget.makeWith({}, stopwatchWidgetElements),
|
||||||
|
buttonMenu: buttonMenuView.makeWith({
|
||||||
|
header: "Header"
|
||||||
|
}, [
|
||||||
|
{ type: "common", label: "Test" },
|
||||||
|
{ type: "control", label: "Test2" },
|
||||||
|
]),
|
||||||
|
buttonPanel: buttonPanelView.makeWith({
|
||||||
|
matrixSizeX: 2,
|
||||||
|
matrixSizeY: 2,
|
||||||
|
}, [
|
||||||
|
{ type: "button", x: 0, y: 0, matrixX: 0, matrixY: 0, icon: offIcons[0], iconSelected: offIcons[1] },
|
||||||
|
{ type: "button", x: 30, y: 30, matrixX: 1, matrixY: 1, icon: powerIcons[0], iconSelected: powerIcons[1] },
|
||||||
|
{ type: "label", x: 0, y: 50, text: "Label", font: "primary" },
|
||||||
|
]),
|
||||||
|
menu: menuView.makeWith({}, [
|
||||||
|
{ label: "One", icon: settingsIcon },
|
||||||
|
{ label: "Two", icon: settingsIcon },
|
||||||
|
{ label: "three", icon: settingsIcon },
|
||||||
|
]),
|
||||||
|
numberKbd: numberInputView.makeWith({
|
||||||
|
header: "Number input",
|
||||||
|
defaultValue: 100,
|
||||||
|
minValue: 0,
|
||||||
|
maxValue: 200,
|
||||||
|
}),
|
||||||
|
popup: popupView.makeWith({
|
||||||
|
header: "Hello",
|
||||||
|
text: "I'm going to be gone\nin 2 seconds",
|
||||||
|
}),
|
||||||
|
viList: viListView.makeWith({}, [
|
||||||
|
{ label: "One", variants: ["1", "1.0"] },
|
||||||
|
{ label: "Two", variants: ["2", "2.0"] },
|
||||||
|
]),
|
||||||
demos: submenuView.makeWith({
|
demos: submenuView.makeWith({
|
||||||
header: "Choose a demo",
|
header: "Choose a demo",
|
||||||
items: [
|
}, [
|
||||||
"Hourglass screen",
|
"Hourglass screen",
|
||||||
"Empty screen",
|
"Empty screen",
|
||||||
"Text input & Dialog",
|
"Text input & Dialog",
|
||||||
@@ -58,9 +102,14 @@ let views = {
|
|||||||
"Text box",
|
"Text box",
|
||||||
"File picker",
|
"File picker",
|
||||||
"Widget",
|
"Widget",
|
||||||
|
"Button menu",
|
||||||
|
"Button panel",
|
||||||
|
"Menu",
|
||||||
|
"Number input",
|
||||||
|
"Popup",
|
||||||
|
"Var. item list",
|
||||||
"Exit app",
|
"Exit app",
|
||||||
],
|
]),
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Enable illegal filename symbols since we're not choosing filenames, gives more flexibility
|
// Enable illegal filename symbols since we're not choosing filenames, gives more flexibility
|
||||||
@@ -98,6 +147,19 @@ eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, v
|
|||||||
} else if (index === 6) {
|
} else if (index === 6) {
|
||||||
gui.viewDispatcher.switchTo(views.stopwatchWidget);
|
gui.viewDispatcher.switchTo(views.stopwatchWidget);
|
||||||
} else if (index === 7) {
|
} else if (index === 7) {
|
||||||
|
gui.viewDispatcher.switchTo(views.buttonMenu);
|
||||||
|
} else if (index === 8) {
|
||||||
|
gui.viewDispatcher.switchTo(views.buttonPanel);
|
||||||
|
} else if (index === 9) {
|
||||||
|
gui.viewDispatcher.switchTo(views.menu);
|
||||||
|
} else if (index === 10) {
|
||||||
|
gui.viewDispatcher.switchTo(views.numberKbd);
|
||||||
|
} else if (index === 11) {
|
||||||
|
views.popup.set("timeout", 2000);
|
||||||
|
gui.viewDispatcher.switchTo(views.popup);
|
||||||
|
} else if (index === 12) {
|
||||||
|
gui.viewDispatcher.switchTo(views.viList);
|
||||||
|
} else if (index === 13) {
|
||||||
eventLoop.stop();
|
eventLoop.stop();
|
||||||
}
|
}
|
||||||
}, gui, eventLoop, views);
|
}, gui, eventLoop, views);
|
||||||
@@ -162,6 +224,42 @@ eventLoop.subscribe(eventLoop.timer("periodic", 500), function (_sub, _item, vie
|
|||||||
return [views, stopwatchWidgetElements, halfSeconds];
|
return [views, stopwatchWidgetElements, halfSeconds];
|
||||||
}, views, stopwatchWidgetElements, 0);
|
}, views, stopwatchWidgetElements, 0);
|
||||||
|
|
||||||
|
// go back after popup times out
|
||||||
|
eventLoop.subscribe(views.popup.timeout, function (_sub, _item, gui, views) {
|
||||||
|
gui.viewDispatcher.switchTo(views.demos);
|
||||||
|
}, gui, views);
|
||||||
|
|
||||||
|
// button menu callback
|
||||||
|
eventLoop.subscribe(views.buttonMenu.input, function (_sub, input, gui, views) {
|
||||||
|
views.helloDialog.set("text", "You selected #" + input.index.toString());
|
||||||
|
views.helloDialog.set("center", "Cool!");
|
||||||
|
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||||
|
}, gui, views);
|
||||||
|
|
||||||
|
// button panel callback
|
||||||
|
eventLoop.subscribe(views.buttonPanel.input, function (_sub, input, gui, views) {
|
||||||
|
views.helloDialog.set("text", "You selected #" + input.index.toString());
|
||||||
|
views.helloDialog.set("center", "Cool!");
|
||||||
|
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||||
|
}, gui, views);
|
||||||
|
|
||||||
|
// menu callback
|
||||||
|
eventLoop.subscribe(views.menu.chosen, function (_sub, index, gui, views) {
|
||||||
|
views.helloDialog.set("text", "You selected #" + index.toString());
|
||||||
|
views.helloDialog.set("center", "Cool!");
|
||||||
|
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||||
|
}, gui, views);
|
||||||
|
|
||||||
|
// menu callback
|
||||||
|
eventLoop.subscribe(views.numberKbd.input, function (_sub, number, gui, views) {
|
||||||
|
views.helloDialog.set("text", "You typed " + number.toString());
|
||||||
|
views.helloDialog.set("center", "Cool!");
|
||||||
|
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||||
|
}, gui, views);
|
||||||
|
|
||||||
|
// ignore VI list
|
||||||
|
eventLoop.subscribe(views.viList.valueUpdate, function (_sub, _item) {});
|
||||||
|
|
||||||
// run UI
|
// run UI
|
||||||
gui.viewDispatcher.switchTo(views.demos);
|
gui.viewDispatcher.switchTo(views.demos);
|
||||||
eventLoop.run();
|
eventLoop.run();
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ typedef struct {
|
|||||||
// - an rbtree because i deemed it more tedious to implement, and with the
|
// - an rbtree because i deemed it more tedious to implement, and with the
|
||||||
// amount of modules in use (under 10 in the overwhelming majority of cases)
|
// amount of modules in use (under 10 in the overwhelming majority of cases)
|
||||||
// i bet it's going to be slower than a plain array
|
// i bet it's going to be slower than a plain array
|
||||||
ARRAY_DEF(JsModuleArray, JsModuleData, M_POD_OPLIST);
|
ARRAY_DEF(JsModuleArray, JsModuleData, M_POD_OPLIST); //-V658
|
||||||
#define M_OPL_JsModuleArray_t() ARRAY_OPLIST(JsModuleArray)
|
#define M_OPL_JsModuleArray_t() ARRAY_OPLIST(JsModuleArray)
|
||||||
|
|
||||||
static const JsModuleDescriptor modules_builtin[] = {
|
static const JsModuleDescriptor modules_builtin[] = {
|
||||||
|
|||||||
169
applications/system/js_app/modules/js_gui/button_menu.c
Normal file
169
applications/system/js_app/modules/js_gui/button_menu.c
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
#include "../../js_modules.h" // IWYU pragma: keep
|
||||||
|
#include "js_gui.h"
|
||||||
|
#include "../js_event_loop/js_event_loop.h"
|
||||||
|
#include <gui/modules/button_menu.h>
|
||||||
|
#include <toolbox/str_buffer.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t next_index;
|
||||||
|
StrBuffer str_buffer;
|
||||||
|
|
||||||
|
FuriMessageQueue* input_queue;
|
||||||
|
JsEventLoopContract contract;
|
||||||
|
} JsBtnMenuContext;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t index;
|
||||||
|
InputType input_type;
|
||||||
|
} JsBtnMenuEvent;
|
||||||
|
|
||||||
|
static const char* js_input_type_to_str(InputType type) {
|
||||||
|
switch(type) {
|
||||||
|
case InputTypePress:
|
||||||
|
return "press";
|
||||||
|
case InputTypeRelease:
|
||||||
|
return "release";
|
||||||
|
case InputTypeShort:
|
||||||
|
return "short";
|
||||||
|
case InputTypeLong:
|
||||||
|
return "long";
|
||||||
|
case InputTypeRepeat:
|
||||||
|
return "repeat";
|
||||||
|
default:
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static mjs_val_t
|
||||||
|
input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsBtnMenuContext* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
JsBtnMenuEvent event;
|
||||||
|
furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk);
|
||||||
|
|
||||||
|
mjs_val_t event_obj = mjs_mk_object(mjs);
|
||||||
|
JS_ASSIGN_MULTI(mjs, event_obj) {
|
||||||
|
JS_FIELD("index", mjs_mk_number(mjs, event.index));
|
||||||
|
JS_FIELD("type", mjs_mk_string(mjs, js_input_type_to_str(event.input_type), ~0, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
return event_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void input_callback(void* ctx, int32_t index, InputType type) {
|
||||||
|
JsBtnMenuContext* context = ctx;
|
||||||
|
JsBtnMenuEvent event = {
|
||||||
|
.index = index,
|
||||||
|
.input_type = type,
|
||||||
|
};
|
||||||
|
furi_check(furi_message_queue_put(context->input_queue, &event, 0) == FuriStatusOk);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool matrix_header_assign(
|
||||||
|
struct mjs* mjs,
|
||||||
|
ButtonMenu* menu,
|
||||||
|
JsViewPropValue value,
|
||||||
|
JsBtnMenuContext* context) {
|
||||||
|
UNUSED(mjs);
|
||||||
|
button_menu_set_header(menu, str_buffer_make_owned_clone(&context->str_buffer, value.string));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool js_button_menu_add_child(
|
||||||
|
struct mjs* mjs,
|
||||||
|
ButtonMenu* menu,
|
||||||
|
JsBtnMenuContext* context,
|
||||||
|
mjs_val_t child_obj) {
|
||||||
|
static const JsValueEnumVariant js_button_menu_item_type_variants[] = {
|
||||||
|
{"common", ButtonMenuItemTypeCommon},
|
||||||
|
{"control", ButtonMenuItemTypeControl},
|
||||||
|
};
|
||||||
|
static const JsValueDeclaration js_button_menu_item_type =
|
||||||
|
JS_VALUE_ENUM(ButtonMenuItemType, js_button_menu_item_type_variants);
|
||||||
|
|
||||||
|
static const JsValueDeclaration js_button_menu_string = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||||
|
|
||||||
|
static const JsValueObjectField js_button_menu_child_fields[] = {
|
||||||
|
{"type", &js_button_menu_item_type},
|
||||||
|
{"label", &js_button_menu_string},
|
||||||
|
};
|
||||||
|
static const JsValueDeclaration js_button_menu_child =
|
||||||
|
JS_VALUE_OBJECT(js_button_menu_child_fields);
|
||||||
|
|
||||||
|
ButtonMenuItemType item_type;
|
||||||
|
const char* label;
|
||||||
|
JsValueParseStatus status;
|
||||||
|
JS_VALUE_PARSE(
|
||||||
|
mjs,
|
||||||
|
JS_VALUE_PARSE_SOURCE_VALUE(&js_button_menu_child),
|
||||||
|
JsValueParseFlagReturnOnError,
|
||||||
|
&status,
|
||||||
|
&child_obj,
|
||||||
|
&item_type,
|
||||||
|
&label);
|
||||||
|
if(status != JsValueParseStatusOk) return false;
|
||||||
|
|
||||||
|
button_menu_add_item(
|
||||||
|
menu,
|
||||||
|
str_buffer_make_owned_clone(&context->str_buffer, label),
|
||||||
|
context->next_index++,
|
||||||
|
input_callback,
|
||||||
|
item_type,
|
||||||
|
context);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void js_button_menu_reset_children(ButtonMenu* menu, JsBtnMenuContext* context) {
|
||||||
|
context->next_index = 0;
|
||||||
|
button_menu_reset(menu);
|
||||||
|
str_buffer_clear_all_clones(&context->str_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static JsBtnMenuContext* ctx_make(struct mjs* mjs, ButtonMenu* menu, mjs_val_t view_obj) {
|
||||||
|
UNUSED(menu);
|
||||||
|
JsBtnMenuContext* context = malloc(sizeof(JsBtnMenuContext));
|
||||||
|
*context = (JsBtnMenuContext){
|
||||||
|
.next_index = 0,
|
||||||
|
.str_buffer = {0},
|
||||||
|
.input_queue = furi_message_queue_alloc(1, sizeof(JsBtnMenuEvent)),
|
||||||
|
};
|
||||||
|
context->contract = (JsEventLoopContract){
|
||||||
|
.magic = JsForeignMagic_JsEventLoopContract,
|
||||||
|
.object_type = JsEventLoopObjectTypeQueue,
|
||||||
|
.object = context->input_queue,
|
||||||
|
.non_timer =
|
||||||
|
{
|
||||||
|
.event = FuriEventLoopEventIn,
|
||||||
|
.transformer = (JsEventLoopTransformer)input_transformer,
|
||||||
|
.transformer_context = context,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract));
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ctx_destroy(ButtonMenu* input, JsBtnMenuContext* context, FuriEventLoop* loop) {
|
||||||
|
UNUSED(input);
|
||||||
|
furi_event_loop_maybe_unsubscribe(loop, context->input_queue);
|
||||||
|
furi_message_queue_free(context->input_queue);
|
||||||
|
str_buffer_clear_all_clones(&context->str_buffer);
|
||||||
|
free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const JsViewDescriptor view_descriptor = {
|
||||||
|
.alloc = (JsViewAlloc)button_menu_alloc,
|
||||||
|
.free = (JsViewFree)button_menu_free,
|
||||||
|
.get_view = (JsViewGetView)button_menu_get_view,
|
||||||
|
.custom_make = (JsViewCustomMake)ctx_make,
|
||||||
|
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
||||||
|
.add_child = (JsViewAddChild)js_button_menu_add_child,
|
||||||
|
.reset_children = (JsViewResetChildren)js_button_menu_reset_children,
|
||||||
|
.prop_cnt = 1,
|
||||||
|
.props = {
|
||||||
|
(JsViewPropDescriptor){
|
||||||
|
.name = "header",
|
||||||
|
.type = JsViewPropTypeString,
|
||||||
|
.assign = (JsViewPropAssign)matrix_header_assign},
|
||||||
|
}};
|
||||||
|
|
||||||
|
JS_GUI_VIEW_DEF(button_menu, &view_descriptor);
|
||||||
274
applications/system/js_app/modules/js_gui/button_panel.c
Normal file
274
applications/system/js_app/modules/js_gui/button_panel.c
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
#include "../../js_modules.h" // IWYU pragma: keep
|
||||||
|
#include "js_gui.h"
|
||||||
|
#include "../js_event_loop/js_event_loop.h"
|
||||||
|
#include <gui/modules/button_panel.h>
|
||||||
|
#include <toolbox/str_buffer.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
size_t matrix_x, matrix_y;
|
||||||
|
int32_t next_index;
|
||||||
|
StrBuffer str_buffer;
|
||||||
|
|
||||||
|
FuriMessageQueue* input_queue;
|
||||||
|
JsEventLoopContract contract;
|
||||||
|
} JsBtnPanelContext;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t index;
|
||||||
|
InputType input_type;
|
||||||
|
} JsBtnPanelEvent;
|
||||||
|
|
||||||
|
static const char* js_input_type_to_str(InputType type) {
|
||||||
|
switch(type) {
|
||||||
|
case InputTypePress:
|
||||||
|
return "press";
|
||||||
|
case InputTypeRelease:
|
||||||
|
return "release";
|
||||||
|
case InputTypeShort:
|
||||||
|
return "short";
|
||||||
|
case InputTypeLong:
|
||||||
|
return "long";
|
||||||
|
case InputTypeRepeat:
|
||||||
|
return "repeat";
|
||||||
|
default:
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static mjs_val_t
|
||||||
|
input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsBtnPanelContext* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
JsBtnPanelEvent event;
|
||||||
|
furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk);
|
||||||
|
|
||||||
|
mjs_val_t event_obj = mjs_mk_object(mjs);
|
||||||
|
JS_ASSIGN_MULTI(mjs, event_obj) {
|
||||||
|
JS_FIELD("index", mjs_mk_number(mjs, event.index));
|
||||||
|
JS_FIELD("type", mjs_mk_string(mjs, js_input_type_to_str(event.input_type), ~0, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
return event_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void input_callback(void* ctx, int32_t index, InputType type) {
|
||||||
|
JsBtnPanelContext* context = ctx;
|
||||||
|
JsBtnPanelEvent event = {
|
||||||
|
.index = index,
|
||||||
|
.input_type = type,
|
||||||
|
};
|
||||||
|
furi_check(furi_message_queue_put(context->input_queue, &event, 0) == FuriStatusOk);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool matrix_size_x_assign(
|
||||||
|
struct mjs* mjs,
|
||||||
|
ButtonPanel* panel,
|
||||||
|
JsViewPropValue value,
|
||||||
|
JsBtnPanelContext* context) {
|
||||||
|
UNUSED(mjs);
|
||||||
|
context->matrix_x = value.number;
|
||||||
|
button_panel_reserve(panel, context->matrix_x, context->matrix_y);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool matrix_size_y_assign(
|
||||||
|
struct mjs* mjs,
|
||||||
|
ButtonPanel* panel,
|
||||||
|
JsViewPropValue value,
|
||||||
|
JsBtnPanelContext* context) {
|
||||||
|
UNUSED(mjs);
|
||||||
|
context->matrix_y = value.number;
|
||||||
|
button_panel_reserve(panel, context->matrix_x, context->matrix_y);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool js_button_panel_add_child(
|
||||||
|
struct mjs* mjs,
|
||||||
|
ButtonPanel* panel,
|
||||||
|
JsBtnPanelContext* context,
|
||||||
|
mjs_val_t child_obj) {
|
||||||
|
typedef enum {
|
||||||
|
JsButtonPanelChildTypeButton,
|
||||||
|
JsButtonPanelChildTypeLabel,
|
||||||
|
JsButtonPanelChildTypeIcon,
|
||||||
|
} JsButtonPanelChildType;
|
||||||
|
static const JsValueEnumVariant js_button_panel_child_type_variants[] = {
|
||||||
|
{"button", JsButtonPanelChildTypeButton},
|
||||||
|
{"label", JsButtonPanelChildTypeLabel},
|
||||||
|
{"icon", JsButtonPanelChildTypeIcon},
|
||||||
|
};
|
||||||
|
static const JsValueDeclaration js_button_panel_child_type =
|
||||||
|
JS_VALUE_ENUM(JsButtonPanelChildType, js_button_panel_child_type_variants);
|
||||||
|
|
||||||
|
static const JsValueDeclaration js_button_panel_number = JS_VALUE_SIMPLE(JsValueTypeInt32);
|
||||||
|
static const JsValueObjectField js_button_panel_common_fields[] = {
|
||||||
|
{"type", &js_button_panel_child_type},
|
||||||
|
{"x", &js_button_panel_number},
|
||||||
|
{"y", &js_button_panel_number},
|
||||||
|
};
|
||||||
|
static const JsValueDeclaration js_button_panel_common =
|
||||||
|
JS_VALUE_OBJECT(js_button_panel_common_fields);
|
||||||
|
|
||||||
|
static const JsValueDeclaration js_button_panel_pointer =
|
||||||
|
JS_VALUE_SIMPLE(JsValueTypeRawPointer);
|
||||||
|
static const JsValueObjectField js_button_panel_button_fields[] = {
|
||||||
|
{"matrixX", &js_button_panel_number},
|
||||||
|
{"matrixY", &js_button_panel_number},
|
||||||
|
{"icon", &js_button_panel_pointer},
|
||||||
|
{"iconSelected", &js_button_panel_pointer},
|
||||||
|
};
|
||||||
|
static const JsValueDeclaration js_button_panel_button =
|
||||||
|
JS_VALUE_OBJECT(js_button_panel_button_fields);
|
||||||
|
|
||||||
|
static const JsValueDeclaration js_button_panel_string = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||||
|
static const JsValueObjectField js_button_panel_label_fields[] = {
|
||||||
|
{"text", &js_button_panel_string},
|
||||||
|
{"font", &js_gui_font_declaration},
|
||||||
|
};
|
||||||
|
static const JsValueDeclaration js_button_panel_label =
|
||||||
|
JS_VALUE_OBJECT(js_button_panel_label_fields);
|
||||||
|
|
||||||
|
static const JsValueObjectField js_button_panel_icon_fields[] = {
|
||||||
|
{"icon", &js_button_panel_pointer},
|
||||||
|
};
|
||||||
|
static const JsValueDeclaration js_button_panel_icon =
|
||||||
|
JS_VALUE_OBJECT(js_button_panel_icon_fields);
|
||||||
|
|
||||||
|
JsButtonPanelChildType child_type;
|
||||||
|
int32_t x, y;
|
||||||
|
JsValueParseStatus status;
|
||||||
|
JS_VALUE_PARSE(
|
||||||
|
mjs,
|
||||||
|
JS_VALUE_PARSE_SOURCE_VALUE(&js_button_panel_common),
|
||||||
|
JsValueParseFlagReturnOnError,
|
||||||
|
&status,
|
||||||
|
&child_obj,
|
||||||
|
&child_type,
|
||||||
|
&x,
|
||||||
|
&y);
|
||||||
|
if(status != JsValueParseStatusOk) return false;
|
||||||
|
|
||||||
|
switch(child_type) {
|
||||||
|
case JsButtonPanelChildTypeButton: {
|
||||||
|
int32_t matrix_x, matrix_y;
|
||||||
|
const Icon *icon, *icon_selected;
|
||||||
|
JS_VALUE_PARSE(
|
||||||
|
mjs,
|
||||||
|
JS_VALUE_PARSE_SOURCE_VALUE(&js_button_panel_button),
|
||||||
|
JsValueParseFlagReturnOnError,
|
||||||
|
&status,
|
||||||
|
&child_obj,
|
||||||
|
&matrix_x,
|
||||||
|
&matrix_y,
|
||||||
|
&icon,
|
||||||
|
&icon_selected);
|
||||||
|
if(status != JsValueParseStatusOk) return false;
|
||||||
|
button_panel_add_item(
|
||||||
|
panel,
|
||||||
|
context->next_index++,
|
||||||
|
matrix_x,
|
||||||
|
matrix_y,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
icon,
|
||||||
|
icon_selected,
|
||||||
|
(ButtonItemCallback)input_callback,
|
||||||
|
context);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case JsButtonPanelChildTypeLabel: {
|
||||||
|
const char* text;
|
||||||
|
Font font;
|
||||||
|
JS_VALUE_PARSE(
|
||||||
|
mjs,
|
||||||
|
JS_VALUE_PARSE_SOURCE_VALUE(&js_button_panel_label),
|
||||||
|
JsValueParseFlagReturnOnError,
|
||||||
|
&status,
|
||||||
|
&child_obj,
|
||||||
|
&text,
|
||||||
|
&font);
|
||||||
|
if(status != JsValueParseStatusOk) return false;
|
||||||
|
button_panel_add_label(
|
||||||
|
panel, x, y, font, str_buffer_make_owned_clone(&context->str_buffer, text));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case JsButtonPanelChildTypeIcon: {
|
||||||
|
const Icon* icon;
|
||||||
|
JS_VALUE_PARSE(
|
||||||
|
mjs,
|
||||||
|
JS_VALUE_PARSE_SOURCE_VALUE(&js_button_panel_icon),
|
||||||
|
JsValueParseFlagReturnOnError,
|
||||||
|
&status,
|
||||||
|
&child_obj,
|
||||||
|
&icon);
|
||||||
|
if(status != JsValueParseStatusOk) return false;
|
||||||
|
button_panel_add_icon(panel, x, y, icon);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void js_button_panel_reset_children(ButtonPanel* panel, JsBtnPanelContext* context) {
|
||||||
|
context->next_index = 0;
|
||||||
|
button_panel_reset(panel);
|
||||||
|
button_panel_reserve(panel, context->matrix_x, context->matrix_y);
|
||||||
|
str_buffer_clear_all_clones(&context->str_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static JsBtnPanelContext* ctx_make(struct mjs* mjs, ButtonPanel* panel, mjs_val_t view_obj) {
|
||||||
|
UNUSED(panel);
|
||||||
|
JsBtnPanelContext* context = malloc(sizeof(JsBtnPanelContext));
|
||||||
|
*context = (JsBtnPanelContext){
|
||||||
|
.matrix_x = 1,
|
||||||
|
.matrix_y = 1,
|
||||||
|
.next_index = 0,
|
||||||
|
.str_buffer = {0},
|
||||||
|
.input_queue = furi_message_queue_alloc(1, sizeof(JsBtnPanelEvent)),
|
||||||
|
};
|
||||||
|
context->contract = (JsEventLoopContract){
|
||||||
|
.magic = JsForeignMagic_JsEventLoopContract,
|
||||||
|
.object_type = JsEventLoopObjectTypeQueue,
|
||||||
|
.object = context->input_queue,
|
||||||
|
.non_timer =
|
||||||
|
{
|
||||||
|
.event = FuriEventLoopEventIn,
|
||||||
|
.transformer = (JsEventLoopTransformer)input_transformer,
|
||||||
|
.transformer_context = context,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract));
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ctx_destroy(ButtonPanel* input, JsBtnPanelContext* context, FuriEventLoop* loop) {
|
||||||
|
UNUSED(input);
|
||||||
|
furi_event_loop_maybe_unsubscribe(loop, context->input_queue);
|
||||||
|
furi_message_queue_free(context->input_queue);
|
||||||
|
str_buffer_clear_all_clones(&context->str_buffer);
|
||||||
|
free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const JsViewDescriptor view_descriptor = {
|
||||||
|
.alloc = (JsViewAlloc)button_panel_alloc,
|
||||||
|
.free = (JsViewFree)button_panel_free,
|
||||||
|
.get_view = (JsViewGetView)button_panel_get_view,
|
||||||
|
.custom_make = (JsViewCustomMake)ctx_make,
|
||||||
|
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
||||||
|
.add_child = (JsViewAddChild)js_button_panel_add_child,
|
||||||
|
.reset_children = (JsViewResetChildren)js_button_panel_reset_children,
|
||||||
|
.prop_cnt = 2,
|
||||||
|
.props = {
|
||||||
|
(JsViewPropDescriptor){
|
||||||
|
.name = "matrixSizeX",
|
||||||
|
.type = JsViewPropTypeNumber,
|
||||||
|
.assign = (JsViewPropAssign)matrix_size_x_assign},
|
||||||
|
(JsViewPropDescriptor){
|
||||||
|
.name = "matrixSizeY",
|
||||||
|
.type = JsViewPropTypeNumber,
|
||||||
|
.assign = (JsViewPropAssign)matrix_size_y_assign},
|
||||||
|
}};
|
||||||
|
|
||||||
|
JS_GUI_VIEW_DEF(button_panel, &view_descriptor);
|
||||||
@@ -14,9 +14,19 @@ typedef struct {
|
|||||||
.name = #icon, .data = &I_##icon \
|
.name = #icon, .data = &I_##icon \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define ANIM_ICON_DEF(icon) \
|
||||||
|
(IconDefinition) { \
|
||||||
|
.name = #icon, .data = &A_##icon \
|
||||||
|
}
|
||||||
|
|
||||||
static const IconDefinition builtin_icons[] = {
|
static const IconDefinition builtin_icons[] = {
|
||||||
ICON_DEF(DolphinWait_59x54),
|
ICON_DEF(DolphinWait_59x54),
|
||||||
ICON_DEF(js_script_10px),
|
ICON_DEF(js_script_10px),
|
||||||
|
ICON_DEF(off_19x20),
|
||||||
|
ICON_DEF(off_hover_19x20),
|
||||||
|
ICON_DEF(power_19x20),
|
||||||
|
ICON_DEF(power_hover_19x20),
|
||||||
|
ANIM_ICON_DEF(Settings_14),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Firmware's Icon struct needs a frames array, and uses a small CompressHeader
|
// Firmware's Icon struct needs a frames array, and uses a small CompressHeader
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ typedef struct {
|
|||||||
void* custom_data;
|
void* custom_data;
|
||||||
} JsGuiViewData;
|
} JsGuiViewData;
|
||||||
|
|
||||||
|
static const JsValueEnumVariant js_gui_font_variants[] = {
|
||||||
|
{"primary", FontPrimary},
|
||||||
|
{"secondary", FontSecondary},
|
||||||
|
{"keyboard", FontKeyboard},
|
||||||
|
{"bit_numbers", FontBigNumbers},
|
||||||
|
};
|
||||||
|
const JsValueDeclaration js_gui_font_declaration = JS_VALUE_ENUM(Font, js_gui_font_variants);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Transformer for custom events
|
* @brief Transformer for custom events
|
||||||
*/
|
*/
|
||||||
@@ -273,9 +281,12 @@ static bool
|
|||||||
/**
|
/**
|
||||||
* @brief Sets the list of children. Not available from JS.
|
* @brief Sets the list of children. Not available from JS.
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool js_gui_view_internal_set_children(
|
||||||
js_gui_view_internal_set_children(struct mjs* mjs, mjs_val_t children, JsGuiViewData* data) {
|
struct mjs* mjs,
|
||||||
data->descriptor->reset_children(data->specific_view, data->custom_data);
|
mjs_val_t children,
|
||||||
|
JsGuiViewData* data,
|
||||||
|
bool do_reset) {
|
||||||
|
if(do_reset) data->descriptor->reset_children(data->specific_view, data->custom_data);
|
||||||
|
|
||||||
for(size_t i = 0; i < mjs_array_length(mjs, children); i++) {
|
for(size_t i = 0; i < mjs_array_length(mjs, children); i++) {
|
||||||
mjs_val_t child = mjs_array_get(mjs, children, i);
|
mjs_val_t child = mjs_array_get(mjs, children, i);
|
||||||
@@ -357,7 +368,7 @@ static void js_gui_view_set_children(struct mjs* mjs) {
|
|||||||
if(!data->descriptor->add_child || !data->descriptor->reset_children)
|
if(!data->descriptor->add_child || !data->descriptor->reset_children)
|
||||||
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
|
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
|
||||||
|
|
||||||
js_gui_view_internal_set_children(mjs, children, data);
|
js_gui_view_internal_set_children(mjs, children, data, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -450,7 +461,7 @@ static void js_gui_vf_make_with(struct mjs* mjs) {
|
|||||||
if(!data->descriptor->add_child || !data->descriptor->reset_children)
|
if(!data->descriptor->add_child || !data->descriptor->reset_children)
|
||||||
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
|
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
|
||||||
|
|
||||||
if(!js_gui_view_internal_set_children(mjs, children, data)) return;
|
if(!js_gui_view_internal_set_children(mjs, children, data, false)) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mjs_return(mjs, view_obj);
|
mjs_return(mjs, view_obj);
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ typedef union {
|
|||||||
mjs_val_t term;
|
mjs_val_t term;
|
||||||
} JsViewPropValue;
|
} JsViewPropValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JS-to-C font enum mapping
|
||||||
|
*/
|
||||||
|
extern const JsValueDeclaration js_gui_font_declaration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Assigns a value to a view property
|
* @brief Assigns a value to a view property
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "js_gui.h"
|
#include "js_gui.h"
|
||||||
|
|
||||||
static constexpr auto js_gui_api_table = sort(create_array_t<sym_entry>(
|
static constexpr auto js_gui_api_table = sort(create_array_t<sym_entry>(
|
||||||
API_METHOD(js_gui_make_view_factory, mjs_val_t, (struct mjs*, const JsViewDescriptor*))));
|
API_METHOD(js_gui_make_view_factory, mjs_val_t, (struct mjs*, const JsViewDescriptor*)),
|
||||||
|
API_VARIABLE(js_gui_font_declaration, const JsValueDeclaration)));
|
||||||
|
|||||||
105
applications/system/js_app/modules/js_gui/menu.c
Normal file
105
applications/system/js_app/modules/js_gui/menu.c
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
#include "../../js_modules.h" // IWYU pragma: keep
|
||||||
|
#include "js_gui.h"
|
||||||
|
#include "../js_event_loop/js_event_loop.h"
|
||||||
|
#include <gui/modules/menu.h>
|
||||||
|
#include <toolbox/str_buffer.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t next_index;
|
||||||
|
StrBuffer str_buffer;
|
||||||
|
|
||||||
|
FuriMessageQueue* queue;
|
||||||
|
JsEventLoopContract contract;
|
||||||
|
} JsMenuCtx;
|
||||||
|
|
||||||
|
static mjs_val_t choose_transformer(struct mjs* mjs, FuriMessageQueue* queue, void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
uint32_t index;
|
||||||
|
furi_check(furi_message_queue_get(queue, &index, 0) == FuriStatusOk);
|
||||||
|
return mjs_mk_number(mjs, (double)index);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void choose_callback(void* context, uint32_t index) {
|
||||||
|
JsMenuCtx* ctx = context;
|
||||||
|
furi_check(furi_message_queue_put(ctx->queue, &index, 0) == FuriStatusOk);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
js_menu_add_child(struct mjs* mjs, Menu* menu, JsMenuCtx* context, mjs_val_t child_obj) {
|
||||||
|
static const JsValueDeclaration js_menu_string = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||||
|
static const JsValueDeclaration js_menu_pointer = JS_VALUE_SIMPLE(JsValueTypeRawPointer);
|
||||||
|
|
||||||
|
static const JsValueObjectField js_menu_child_fields[] = {
|
||||||
|
{"icon", &js_menu_pointer},
|
||||||
|
{"label", &js_menu_string},
|
||||||
|
};
|
||||||
|
static const JsValueDeclaration js_menu_child = JS_VALUE_OBJECT(js_menu_child_fields);
|
||||||
|
|
||||||
|
const Icon* icon;
|
||||||
|
const char* label;
|
||||||
|
JsValueParseStatus status;
|
||||||
|
JS_VALUE_PARSE(
|
||||||
|
mjs,
|
||||||
|
JS_VALUE_PARSE_SOURCE_VALUE(&js_menu_child),
|
||||||
|
JsValueParseFlagReturnOnError,
|
||||||
|
&status,
|
||||||
|
&child_obj,
|
||||||
|
&icon,
|
||||||
|
&label);
|
||||||
|
if(status != JsValueParseStatusOk) return false;
|
||||||
|
|
||||||
|
menu_add_item(
|
||||||
|
menu,
|
||||||
|
str_buffer_make_owned_clone(&context->str_buffer, label),
|
||||||
|
icon,
|
||||||
|
context->next_index++,
|
||||||
|
choose_callback,
|
||||||
|
context);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void js_menu_reset_children(Menu* menu, JsMenuCtx* context) {
|
||||||
|
context->next_index = 0;
|
||||||
|
menu_reset(menu);
|
||||||
|
str_buffer_clear_all_clones(&context->str_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static JsMenuCtx* ctx_make(struct mjs* mjs, Menu* input, mjs_val_t view_obj) {
|
||||||
|
UNUSED(input);
|
||||||
|
JsMenuCtx* context = malloc(sizeof(JsMenuCtx));
|
||||||
|
context->queue = furi_message_queue_alloc(1, sizeof(uint32_t));
|
||||||
|
context->contract = (JsEventLoopContract){
|
||||||
|
.magic = JsForeignMagic_JsEventLoopContract,
|
||||||
|
.object_type = JsEventLoopObjectTypeQueue,
|
||||||
|
.object = context->queue,
|
||||||
|
.non_timer =
|
||||||
|
{
|
||||||
|
.event = FuriEventLoopEventIn,
|
||||||
|
.transformer = (JsEventLoopTransformer)choose_transformer,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mjs_set(mjs, view_obj, "chosen", ~0, mjs_mk_foreign(mjs, &context->contract));
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ctx_destroy(Menu* input, JsMenuCtx* context, FuriEventLoop* loop) {
|
||||||
|
UNUSED(input);
|
||||||
|
furi_event_loop_maybe_unsubscribe(loop, context->queue);
|
||||||
|
furi_message_queue_free(context->queue);
|
||||||
|
str_buffer_clear_all_clones(&context->str_buffer);
|
||||||
|
free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const JsViewDescriptor view_descriptor = {
|
||||||
|
.alloc = (JsViewAlloc)menu_alloc,
|
||||||
|
.free = (JsViewFree)menu_free,
|
||||||
|
.get_view = (JsViewGetView)menu_get_view,
|
||||||
|
.custom_make = (JsViewCustomMake)ctx_make,
|
||||||
|
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
||||||
|
.add_child = (JsViewAddChild)js_menu_add_child,
|
||||||
|
.reset_children = (JsViewResetChildren)js_menu_reset_children,
|
||||||
|
.prop_cnt = 0,
|
||||||
|
.props = {},
|
||||||
|
};
|
||||||
|
JS_GUI_VIEW_DEF(menu, &view_descriptor);
|
||||||
130
applications/system/js_app/modules/js_gui/number_input.c
Normal file
130
applications/system/js_app/modules/js_gui/number_input.c
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
#include "../../js_modules.h" // IWYU pragma: keep
|
||||||
|
#include "js_gui.h"
|
||||||
|
#include "../js_event_loop/js_event_loop.h"
|
||||||
|
#include <gui/modules/number_input.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t default_val, min_val, max_val;
|
||||||
|
FuriMessageQueue* input_queue;
|
||||||
|
JsEventLoopContract contract;
|
||||||
|
} JsNumKbdContext;
|
||||||
|
|
||||||
|
static mjs_val_t
|
||||||
|
input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsNumKbdContext* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
int32_t number;
|
||||||
|
furi_check(furi_message_queue_get(queue, &number, 0) == FuriStatusOk);
|
||||||
|
return mjs_mk_number(mjs, number);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void input_callback(void* ctx, int32_t value) {
|
||||||
|
JsNumKbdContext* context = ctx;
|
||||||
|
furi_check(furi_message_queue_put(context->input_queue, &value, 0) == FuriStatusOk);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool header_assign(
|
||||||
|
struct mjs* mjs,
|
||||||
|
NumberInput* input,
|
||||||
|
JsViewPropValue value,
|
||||||
|
JsNumKbdContext* context) {
|
||||||
|
UNUSED(mjs);
|
||||||
|
UNUSED(context);
|
||||||
|
number_input_set_header_text(input, value.string);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool min_val_assign(
|
||||||
|
struct mjs* mjs,
|
||||||
|
NumberInput* input,
|
||||||
|
JsViewPropValue value,
|
||||||
|
JsNumKbdContext* context) {
|
||||||
|
UNUSED(mjs);
|
||||||
|
context->min_val = value.number;
|
||||||
|
number_input_set_result_callback(
|
||||||
|
input, input_callback, context, context->default_val, context->min_val, context->max_val);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool max_val_assign(
|
||||||
|
struct mjs* mjs,
|
||||||
|
NumberInput* input,
|
||||||
|
JsViewPropValue value,
|
||||||
|
JsNumKbdContext* context) {
|
||||||
|
UNUSED(mjs);
|
||||||
|
context->max_val = value.number;
|
||||||
|
number_input_set_result_callback(
|
||||||
|
input, input_callback, context, context->default_val, context->min_val, context->max_val);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool default_val_assign(
|
||||||
|
struct mjs* mjs,
|
||||||
|
NumberInput* input,
|
||||||
|
JsViewPropValue value,
|
||||||
|
JsNumKbdContext* context) {
|
||||||
|
UNUSED(mjs);
|
||||||
|
context->default_val = value.number;
|
||||||
|
number_input_set_result_callback(
|
||||||
|
input, input_callback, context, context->default_val, context->min_val, context->max_val);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static JsNumKbdContext* ctx_make(struct mjs* mjs, NumberInput* input, mjs_val_t view_obj) {
|
||||||
|
JsNumKbdContext* context = malloc(sizeof(JsNumKbdContext));
|
||||||
|
*context = (JsNumKbdContext){
|
||||||
|
.default_val = 0,
|
||||||
|
.max_val = 100,
|
||||||
|
.min_val = 0,
|
||||||
|
.input_queue = furi_message_queue_alloc(1, sizeof(int32_t)),
|
||||||
|
};
|
||||||
|
context->contract = (JsEventLoopContract){
|
||||||
|
.magic = JsForeignMagic_JsEventLoopContract,
|
||||||
|
.object_type = JsEventLoopObjectTypeQueue,
|
||||||
|
.object = context->input_queue,
|
||||||
|
.non_timer =
|
||||||
|
{
|
||||||
|
.event = FuriEventLoopEventIn,
|
||||||
|
.transformer = (JsEventLoopTransformer)input_transformer,
|
||||||
|
.transformer_context = context,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
number_input_set_result_callback(
|
||||||
|
input, input_callback, context, context->default_val, context->min_val, context->max_val);
|
||||||
|
mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract));
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ctx_destroy(NumberInput* input, JsNumKbdContext* context, FuriEventLoop* loop) {
|
||||||
|
UNUSED(input);
|
||||||
|
furi_event_loop_maybe_unsubscribe(loop, context->input_queue);
|
||||||
|
furi_message_queue_free(context->input_queue);
|
||||||
|
free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const JsViewDescriptor view_descriptor = {
|
||||||
|
.alloc = (JsViewAlloc)number_input_alloc,
|
||||||
|
.free = (JsViewFree)number_input_free,
|
||||||
|
.get_view = (JsViewGetView)number_input_get_view,
|
||||||
|
.custom_make = (JsViewCustomMake)ctx_make,
|
||||||
|
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
||||||
|
.prop_cnt = 4,
|
||||||
|
.props = {
|
||||||
|
(JsViewPropDescriptor){
|
||||||
|
.name = "header",
|
||||||
|
.type = JsViewPropTypeString,
|
||||||
|
.assign = (JsViewPropAssign)header_assign},
|
||||||
|
(JsViewPropDescriptor){
|
||||||
|
.name = "minValue",
|
||||||
|
.type = JsViewPropTypeNumber,
|
||||||
|
.assign = (JsViewPropAssign)min_val_assign},
|
||||||
|
(JsViewPropDescriptor){
|
||||||
|
.name = "maxValue",
|
||||||
|
.type = JsViewPropTypeNumber,
|
||||||
|
.assign = (JsViewPropAssign)max_val_assign},
|
||||||
|
(JsViewPropDescriptor){
|
||||||
|
.name = "defaultValue",
|
||||||
|
.type = JsViewPropTypeNumber,
|
||||||
|
.assign = (JsViewPropAssign)default_val_assign},
|
||||||
|
}};
|
||||||
|
|
||||||
|
JS_GUI_VIEW_DEF(number_input, &view_descriptor);
|
||||||
102
applications/system/js_app/modules/js_gui/popup.c
Normal file
102
applications/system/js_app/modules/js_gui/popup.c
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
#include "../../js_modules.h" // IWYU pragma: keep
|
||||||
|
#include "js_gui.h"
|
||||||
|
#include "../js_event_loop/js_event_loop.h"
|
||||||
|
#include <gui/modules/popup.h>
|
||||||
|
#include <toolbox/str_buffer.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
StrBuffer str_buffer;
|
||||||
|
FuriSemaphore* semaphore;
|
||||||
|
JsEventLoopContract contract;
|
||||||
|
} JsPopupCtx;
|
||||||
|
|
||||||
|
static void timeout_callback(JsPopupCtx* context) {
|
||||||
|
furi_check(furi_semaphore_release(context->semaphore) == FuriStatusOk);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
header_assign(struct mjs* mjs, Popup* popup, JsViewPropValue value, JsPopupCtx* context) {
|
||||||
|
UNUSED(mjs);
|
||||||
|
UNUSED(context);
|
||||||
|
popup_set_header(
|
||||||
|
popup,
|
||||||
|
str_buffer_make_owned_clone(&context->str_buffer, value.string),
|
||||||
|
64,
|
||||||
|
0,
|
||||||
|
AlignCenter,
|
||||||
|
AlignTop);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
text_assign(struct mjs* mjs, Popup* popup, JsViewPropValue value, JsPopupCtx* context) {
|
||||||
|
UNUSED(mjs);
|
||||||
|
UNUSED(context);
|
||||||
|
popup_set_text(
|
||||||
|
popup,
|
||||||
|
str_buffer_make_owned_clone(&context->str_buffer, value.string),
|
||||||
|
64,
|
||||||
|
32,
|
||||||
|
AlignCenter,
|
||||||
|
AlignCenter);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
timeout_assign(struct mjs* mjs, Popup* popup, JsViewPropValue value, JsPopupCtx* context) {
|
||||||
|
UNUSED(mjs);
|
||||||
|
UNUSED(context);
|
||||||
|
popup_set_timeout(popup, value.number);
|
||||||
|
popup_enable_timeout(popup);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static JsPopupCtx* ctx_make(struct mjs* mjs, Popup* popup, mjs_val_t view_obj) {
|
||||||
|
JsPopupCtx* context = malloc(sizeof(JsPopupCtx));
|
||||||
|
context->semaphore = furi_semaphore_alloc(1, 0);
|
||||||
|
context->contract = (JsEventLoopContract){
|
||||||
|
.magic = JsForeignMagic_JsEventLoopContract,
|
||||||
|
.object_type = JsEventLoopObjectTypeSemaphore,
|
||||||
|
.object = context->semaphore,
|
||||||
|
.non_timer =
|
||||||
|
{
|
||||||
|
.event = FuriEventLoopEventIn,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mjs_set(mjs, view_obj, "timeout", ~0, mjs_mk_foreign(mjs, &context->contract));
|
||||||
|
popup_set_callback(popup, (PopupCallback)timeout_callback);
|
||||||
|
popup_set_context(popup, context);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ctx_destroy(Popup* popup, JsPopupCtx* context, FuriEventLoop* loop) {
|
||||||
|
UNUSED(popup);
|
||||||
|
furi_event_loop_maybe_unsubscribe(loop, context->semaphore);
|
||||||
|
furi_semaphore_free(context->semaphore);
|
||||||
|
str_buffer_clear_all_clones(&context->str_buffer);
|
||||||
|
free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const JsViewDescriptor view_descriptor = {
|
||||||
|
.alloc = (JsViewAlloc)popup_alloc,
|
||||||
|
.free = (JsViewFree)popup_free,
|
||||||
|
.get_view = (JsViewGetView)popup_get_view,
|
||||||
|
.custom_make = (JsViewCustomMake)ctx_make,
|
||||||
|
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
||||||
|
.prop_cnt = 3,
|
||||||
|
.props = {
|
||||||
|
(JsViewPropDescriptor){
|
||||||
|
.name = "header",
|
||||||
|
.type = JsViewPropTypeString,
|
||||||
|
.assign = (JsViewPropAssign)header_assign},
|
||||||
|
(JsViewPropDescriptor){
|
||||||
|
.name = "text",
|
||||||
|
.type = JsViewPropTypeString,
|
||||||
|
.assign = (JsViewPropAssign)text_assign},
|
||||||
|
(JsViewPropDescriptor){
|
||||||
|
.name = "timeout",
|
||||||
|
.type = JsViewPropTypeNumber,
|
||||||
|
.assign = (JsViewPropAssign)timeout_assign},
|
||||||
|
}};
|
||||||
|
|
||||||
|
JS_GUI_VIEW_DEF(popup, &view_descriptor);
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
#define QUEUE_LEN 2
|
#define QUEUE_LEN 2
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
int32_t next_index;
|
||||||
FuriMessageQueue* queue;
|
FuriMessageQueue* queue;
|
||||||
JsEventLoopContract contract;
|
JsEventLoopContract contract;
|
||||||
} JsSubmenuCtx;
|
} JsSubmenuCtx;
|
||||||
@@ -30,18 +31,24 @@ static bool
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool items_assign(struct mjs* mjs, Submenu* submenu, JsViewPropValue value, void* context) {
|
static bool js_submenu_add_child(
|
||||||
UNUSED(mjs);
|
struct mjs* mjs,
|
||||||
submenu_reset(submenu);
|
Submenu* submenu,
|
||||||
size_t len = mjs_array_length(mjs, value.term);
|
JsSubmenuCtx* context,
|
||||||
for(size_t i = 0; i < len; i++) {
|
mjs_val_t child_obj) {
|
||||||
mjs_val_t item = mjs_array_get(mjs, value.term, i);
|
const char* str = mjs_get_string(mjs, &child_obj, NULL);
|
||||||
if(!mjs_is_string(item)) return false;
|
if(!str) return false;
|
||||||
submenu_add_item(submenu, mjs_get_string(mjs, &item, NULL), i, choose_callback, context);
|
|
||||||
}
|
submenu_add_item(submenu, str, context->next_index++, choose_callback, context);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void js_submenu_reset_children(Submenu* submenu, JsSubmenuCtx* context) {
|
||||||
|
context->next_index = 0;
|
||||||
|
submenu_reset(submenu);
|
||||||
|
}
|
||||||
|
|
||||||
static JsSubmenuCtx* ctx_make(struct mjs* mjs, Submenu* input, mjs_val_t view_obj) {
|
static JsSubmenuCtx* ctx_make(struct mjs* mjs, Submenu* input, mjs_val_t view_obj) {
|
||||||
UNUSED(input);
|
UNUSED(input);
|
||||||
JsSubmenuCtx* context = malloc(sizeof(JsSubmenuCtx));
|
JsSubmenuCtx* context = malloc(sizeof(JsSubmenuCtx));
|
||||||
@@ -73,15 +80,13 @@ static const JsViewDescriptor view_descriptor = {
|
|||||||
.get_view = (JsViewGetView)submenu_get_view,
|
.get_view = (JsViewGetView)submenu_get_view,
|
||||||
.custom_make = (JsViewCustomMake)ctx_make,
|
.custom_make = (JsViewCustomMake)ctx_make,
|
||||||
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
||||||
.prop_cnt = 2,
|
.add_child = (JsViewAddChild)js_submenu_add_child,
|
||||||
|
.reset_children = (JsViewResetChildren)js_submenu_reset_children,
|
||||||
|
.prop_cnt = 1,
|
||||||
.props = {
|
.props = {
|
||||||
(JsViewPropDescriptor){
|
(JsViewPropDescriptor){
|
||||||
.name = "header",
|
.name = "header",
|
||||||
.type = JsViewPropTypeString,
|
.type = JsViewPropTypeString,
|
||||||
.assign = (JsViewPropAssign)header_assign},
|
.assign = (JsViewPropAssign)header_assign},
|
||||||
(JsViewPropDescriptor){
|
|
||||||
.name = "items",
|
|
||||||
.type = JsViewPropTypeArr,
|
|
||||||
.assign = (JsViewPropAssign)items_assign},
|
|
||||||
}};
|
}};
|
||||||
JS_GUI_VIEW_DEF(submenu, &view_descriptor);
|
JS_GUI_VIEW_DEF(submenu, &view_descriptor);
|
||||||
|
|||||||
163
applications/system/js_app/modules/js_gui/vi_list.c
Normal file
163
applications/system/js_app/modules/js_gui/vi_list.c
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
#include "../../js_modules.h" // IWYU pragma: keep
|
||||||
|
#include "js_gui.h"
|
||||||
|
#include "../js_event_loop/js_event_loop.h"
|
||||||
|
#include <gui/modules/variable_item_list.h>
|
||||||
|
#include <toolbox/str_buffer.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
StrBuffer str_buffer;
|
||||||
|
|
||||||
|
// let mjs do the memory management heavy lifting, store children in a js array
|
||||||
|
struct mjs* mjs;
|
||||||
|
mjs_val_t children;
|
||||||
|
VariableItemList* list;
|
||||||
|
|
||||||
|
FuriMessageQueue* input_queue;
|
||||||
|
JsEventLoopContract contract;
|
||||||
|
} JsViListContext;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t item_index;
|
||||||
|
int32_t value_index;
|
||||||
|
} JsViListEvent;
|
||||||
|
|
||||||
|
static mjs_val_t
|
||||||
|
input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsViListContext* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
JsViListEvent event;
|
||||||
|
furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk);
|
||||||
|
|
||||||
|
mjs_val_t event_obj = mjs_mk_object(mjs);
|
||||||
|
JS_ASSIGN_MULTI(mjs, event_obj) {
|
||||||
|
JS_FIELD("itemIndex", mjs_mk_number(mjs, event.item_index));
|
||||||
|
JS_FIELD("valueIndex", mjs_mk_number(mjs, event.value_index));
|
||||||
|
}
|
||||||
|
|
||||||
|
return event_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void js_vi_list_change_callback(VariableItem* item) {
|
||||||
|
JsViListContext* context = variable_item_get_context(item);
|
||||||
|
struct mjs* mjs = context->mjs;
|
||||||
|
uint8_t item_index = variable_item_list_get_selected_item_index(context->list);
|
||||||
|
uint8_t value_index = variable_item_get_current_value_index(item);
|
||||||
|
|
||||||
|
// type safety ensured in add_child
|
||||||
|
mjs_val_t variants = mjs_array_get(mjs, context->children, item_index);
|
||||||
|
mjs_val_t variant = mjs_array_get(mjs, variants, value_index);
|
||||||
|
variable_item_set_current_value_text(item, mjs_get_string(mjs, &variant, NULL));
|
||||||
|
|
||||||
|
JsViListEvent event = {
|
||||||
|
.item_index = item_index,
|
||||||
|
.value_index = value_index,
|
||||||
|
};
|
||||||
|
furi_check(furi_message_queue_put(context->input_queue, &event, 0) == FuriStatusOk);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool js_vi_list_add_child(
|
||||||
|
struct mjs* mjs,
|
||||||
|
VariableItemList* list,
|
||||||
|
JsViListContext* context,
|
||||||
|
mjs_val_t child_obj) {
|
||||||
|
static const JsValueDeclaration js_vi_list_string = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||||
|
static const JsValueDeclaration js_vi_list_arr = JS_VALUE_SIMPLE(JsValueTypeAnyArray);
|
||||||
|
static const JsValueDeclaration js_vi_list_int_default_0 =
|
||||||
|
JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 0);
|
||||||
|
|
||||||
|
static const JsValueObjectField js_vi_list_child_fields[] = {
|
||||||
|
{"label", &js_vi_list_string},
|
||||||
|
{"variants", &js_vi_list_arr},
|
||||||
|
{"defaultSelected", &js_vi_list_int_default_0},
|
||||||
|
};
|
||||||
|
static const JsValueDeclaration js_vi_list_child =
|
||||||
|
JS_VALUE_OBJECT_W_DEFAULTS(js_vi_list_child_fields);
|
||||||
|
|
||||||
|
JsValueParseStatus status;
|
||||||
|
const char* label;
|
||||||
|
mjs_val_t variants;
|
||||||
|
int32_t default_selected;
|
||||||
|
JS_VALUE_PARSE(
|
||||||
|
mjs,
|
||||||
|
JS_VALUE_PARSE_SOURCE_VALUE(&js_vi_list_child),
|
||||||
|
JsValueParseFlagReturnOnError,
|
||||||
|
&status,
|
||||||
|
&child_obj,
|
||||||
|
&label,
|
||||||
|
&variants,
|
||||||
|
&default_selected);
|
||||||
|
if(status != JsValueParseStatusOk) return false;
|
||||||
|
|
||||||
|
size_t variants_cnt = mjs_array_length(mjs, variants);
|
||||||
|
for(size_t i = 0; i < variants_cnt; i++)
|
||||||
|
if(!mjs_is_string(mjs_array_get(mjs, variants, i))) return false;
|
||||||
|
|
||||||
|
VariableItem* item = variable_item_list_add(
|
||||||
|
list,
|
||||||
|
str_buffer_make_owned_clone(&context->str_buffer, label),
|
||||||
|
variants_cnt,
|
||||||
|
js_vi_list_change_callback,
|
||||||
|
context);
|
||||||
|
variable_item_set_current_value_index(item, default_selected);
|
||||||
|
mjs_val_t default_variant = mjs_array_get(mjs, variants, default_selected);
|
||||||
|
variable_item_set_current_value_text(item, mjs_get_string(mjs, &default_variant, NULL));
|
||||||
|
|
||||||
|
mjs_array_push(context->mjs, context->children, variants);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void js_vi_list_reset_children(VariableItemList* list, JsViListContext* context) {
|
||||||
|
mjs_disown(context->mjs, &context->children);
|
||||||
|
context->children = mjs_mk_array(context->mjs);
|
||||||
|
mjs_own(context->mjs, &context->children);
|
||||||
|
|
||||||
|
variable_item_list_reset(list);
|
||||||
|
str_buffer_clear_all_clones(&context->str_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static JsViListContext* ctx_make(struct mjs* mjs, VariableItemList* list, mjs_val_t view_obj) {
|
||||||
|
JsViListContext* context = malloc(sizeof(JsViListContext));
|
||||||
|
*context = (JsViListContext){
|
||||||
|
.str_buffer = {0},
|
||||||
|
.mjs = mjs,
|
||||||
|
.children = mjs_mk_array(mjs),
|
||||||
|
.list = list,
|
||||||
|
.input_queue = furi_message_queue_alloc(1, sizeof(JsViListEvent)),
|
||||||
|
};
|
||||||
|
mjs_own(context->mjs, &context->children);
|
||||||
|
context->contract = (JsEventLoopContract){
|
||||||
|
.magic = JsForeignMagic_JsEventLoopContract,
|
||||||
|
.object_type = JsEventLoopObjectTypeQueue,
|
||||||
|
.object = context->input_queue,
|
||||||
|
.non_timer =
|
||||||
|
{
|
||||||
|
.event = FuriEventLoopEventIn,
|
||||||
|
.transformer = (JsEventLoopTransformer)input_transformer,
|
||||||
|
.transformer_context = context,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mjs_set(mjs, view_obj, "valueUpdate", ~0, mjs_mk_foreign(mjs, &context->contract));
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ctx_destroy(VariableItemList* input, JsViListContext* context, FuriEventLoop* loop) {
|
||||||
|
UNUSED(input);
|
||||||
|
furi_event_loop_maybe_unsubscribe(loop, context->input_queue);
|
||||||
|
furi_message_queue_free(context->input_queue);
|
||||||
|
str_buffer_clear_all_clones(&context->str_buffer);
|
||||||
|
free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const JsViewDescriptor view_descriptor = {
|
||||||
|
.alloc = (JsViewAlloc)variable_item_list_alloc,
|
||||||
|
.free = (JsViewFree)variable_item_list_free,
|
||||||
|
.get_view = (JsViewGetView)variable_item_list_get_view,
|
||||||
|
.custom_make = (JsViewCustomMake)ctx_make,
|
||||||
|
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
||||||
|
.add_child = (JsViewAddChild)js_vi_list_add_child,
|
||||||
|
.reset_children = (JsViewResetChildren)js_vi_list_reset_children,
|
||||||
|
.prop_cnt = 0,
|
||||||
|
.props = {},
|
||||||
|
};
|
||||||
|
|
||||||
|
JS_GUI_VIEW_DEF(vi_list, &view_descriptor);
|
||||||
@@ -20,7 +20,7 @@ typedef struct {
|
|||||||
char* data;
|
char* data;
|
||||||
} PatternArrayItem;
|
} PatternArrayItem;
|
||||||
|
|
||||||
ARRAY_DEF(PatternArray, PatternArrayItem, M_POD_OPLIST);
|
ARRAY_DEF(PatternArray, PatternArrayItem, M_POD_OPLIST); //-V658
|
||||||
|
|
||||||
static void
|
static void
|
||||||
js_serial_on_async_rx(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) {
|
js_serial_on_async_rx(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) {
|
||||||
|
|||||||
40
applications/system/js_app/packages/fz-sdk/gui/button_menu.d.ts
vendored
Normal file
40
applications/system/js_app/packages/fz-sdk/gui/button_menu.d.ts
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Displays a list of buttons.
|
||||||
|
*
|
||||||
|
* <img src="../images/button_menu.png" width="200" alt="Sample screenshot of the view" />
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* let eventLoop = require("event_loop");
|
||||||
|
* let gui = require("gui");
|
||||||
|
* let buttonMenuView = require("gui/button_menu");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* - `header`: Textual header above the buttons
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.4
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory, InputType } from ".";
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
header: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Child = { type: "common" | "control", label: string };
|
||||||
|
|
||||||
|
declare class ButtonMenu extends View<Props, Child> {
|
||||||
|
input: Contract<{ index: number, type: InputType }>;
|
||||||
|
}
|
||||||
|
declare class ButtonMenuFactory extends ViewFactory<Props, Child, ButtonMenu> { }
|
||||||
|
declare const factory: ButtonMenuFactory;
|
||||||
|
export = factory;
|
||||||
49
applications/system/js_app/packages/fz-sdk/gui/button_panel.d.ts
vendored
Normal file
49
applications/system/js_app/packages/fz-sdk/gui/button_panel.d.ts
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Displays a button matrix.
|
||||||
|
*
|
||||||
|
* <img src="../images/button_panel.png" width="200" alt="Sample screenshot of the view" />
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* let eventLoop = require("event_loop");
|
||||||
|
* let gui = require("gui");
|
||||||
|
* let buttonPanelView = require("gui/button_panel");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* - `matrixSizeX`: Width of imaginary grid used for navigation
|
||||||
|
* - `matrixSizeY`: Height of imaginary grid used for navigation
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.4
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory, Font, InputType } from ".";
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
import { IconData } from "./icon";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
matrixSizeX: number,
|
||||||
|
matrixSizeY: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Position = { x: number, y: number };
|
||||||
|
|
||||||
|
type ButtonChild = { type: "button", matrixX: number, matrixY: number, icon: IconData, iconSelected: IconData } & Position;
|
||||||
|
type LabelChild = { type: "label", font: Font, text: string } & Position;
|
||||||
|
type IconChild = { type: "icon", icon: IconData };
|
||||||
|
|
||||||
|
type Child = ButtonChild | LabelChild | IconChild;
|
||||||
|
|
||||||
|
declare class ButtonPanel extends View<Props, Child> {
|
||||||
|
input: Contract<{ index: number, type: InputType }>;
|
||||||
|
}
|
||||||
|
declare class ButtonPanelFactory extends ViewFactory<Props, Child, ButtonPanel> { }
|
||||||
|
declare const factory: ButtonPanelFactory;
|
||||||
|
export = factory;
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
export type BuiltinIcon = "DolphinWait_59x54" | "js_script_10px";
|
export type BuiltinIcon = "DolphinWait_59x54" | "js_script_10px"
|
||||||
|
| "off_19x20" | "off_hover_19x20"
|
||||||
|
| "power_19x20" | "power_hover_19x20"
|
||||||
|
| "Settings_14";
|
||||||
|
|
||||||
export type IconData = symbol & { "__tag__": "icon" };
|
export type IconData = symbol & { "__tag__": "icon" };
|
||||||
// introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist.
|
// introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist.
|
||||||
|
|||||||
@@ -24,24 +24,23 @@
|
|||||||
* ### View
|
* ### View
|
||||||
* In Flipper's terminology, a "View" is a fullscreen design element that
|
* In Flipper's terminology, a "View" is a fullscreen design element that
|
||||||
* 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:
|
||||||
* 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` | ✅ (as `file_picker`) |
|
* | `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` | ✅ (as `vi_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
|
||||||
@@ -119,6 +118,9 @@
|
|||||||
|
|
||||||
import type { Contract } from "../event_loop";
|
import type { Contract } from "../event_loop";
|
||||||
|
|
||||||
|
export type Font = "primary" | "secondary" | "keyboard" | "big_numbers";
|
||||||
|
export type InputType = "press" | "release" | "short" | "long" | "repeat";
|
||||||
|
|
||||||
type Properties = { [K: string]: any };
|
type Properties = { [K: string]: any };
|
||||||
|
|
||||||
export declare class View<Props extends Properties, Child> {
|
export declare class View<Props extends Properties, Child> {
|
||||||
|
|||||||
38
applications/system/js_app/packages/fz-sdk/gui/menu.d.ts
vendored
Normal file
38
applications/system/js_app/packages/fz-sdk/gui/menu.d.ts
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* A list of selectable entries consisting of an icon and a label.
|
||||||
|
*
|
||||||
|
* <img src="../images/menu.png" width="200" alt="Sample screenshot of the view" />
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* let eventLoop = require("event_loop");
|
||||||
|
* let gui = require("gui");
|
||||||
|
* let submenuView = require("gui/menu");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 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 doesn't have any props.
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @version API changed in JS SDK 0.4
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory } from ".";
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
import type { IconData } from "./icon";
|
||||||
|
|
||||||
|
type Props = {};
|
||||||
|
type Child = { icon: IconData, label: string };
|
||||||
|
declare class Submenu extends View<Props, Child> {
|
||||||
|
chosen: Contract<number>;
|
||||||
|
}
|
||||||
|
declare class SubmenuFactory extends ViewFactory<Props, Child, Submenu> { }
|
||||||
|
declare const factory: SubmenuFactory;
|
||||||
|
export = factory;
|
||||||
44
applications/system/js_app/packages/fz-sdk/gui/number_input.d.ts
vendored
Normal file
44
applications/system/js_app/packages/fz-sdk/gui/number_input.d.ts
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Displays a number input keyboard.
|
||||||
|
*
|
||||||
|
* <img src="../images/number_input.png" width="200" alt="Sample screenshot of the view" />
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* let eventLoop = require("event_loop");
|
||||||
|
* let gui = require("gui");
|
||||||
|
* let numberInputView = require("gui/number_input");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* This module depends on the `gui` module, which in turn depends on the
|
||||||
|
* `event_loop` module, so they _must_ be imported in this order. It is also
|
||||||
|
* recommended to conceptualize these modules first before using this one.
|
||||||
|
*
|
||||||
|
* # Example
|
||||||
|
* For an example refer to the `gui.js` example script.
|
||||||
|
*
|
||||||
|
* # View props
|
||||||
|
* - `header`: Text displayed at the top of the screen
|
||||||
|
* - `minValue`: Minimum allowed numeric value
|
||||||
|
* - `maxValue`: Maximum allowed numeric value
|
||||||
|
* - `defaultValue`: Default numeric value
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.4
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory } from ".";
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
header: string,
|
||||||
|
minValue: number,
|
||||||
|
maxValue: number,
|
||||||
|
defaultValue: number,
|
||||||
|
}
|
||||||
|
type Child = never;
|
||||||
|
declare class NumberInput extends View<Props, Child> {
|
||||||
|
input: Contract<number>;
|
||||||
|
}
|
||||||
|
declare class NumberInputFactory extends ViewFactory<Props, Child, NumberInput> { }
|
||||||
|
declare const factory: NumberInputFactory;
|
||||||
|
export = factory;
|
||||||
43
applications/system/js_app/packages/fz-sdk/gui/popup.d.ts
vendored
Normal file
43
applications/system/js_app/packages/fz-sdk/gui/popup.d.ts
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Like a Dialog, but with a built-in timer.
|
||||||
|
*
|
||||||
|
* <img src="../images/popup.png" width="200" alt="Sample screenshot of the view" />
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* let eventLoop = require("event_loop");
|
||||||
|
* let gui = require("gui");
|
||||||
|
* let popupView = require("gui/popup");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* - `header`: Text displayed in bold at the top of the screen
|
||||||
|
* - `text`: Text displayed in the middle of the string
|
||||||
|
* - `timeout`: Timeout, in milliseconds, after which the event will fire. The
|
||||||
|
* timer starts counting down when this property is assigned.
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory } from ".";
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
header: string,
|
||||||
|
text: string,
|
||||||
|
timeout: number,
|
||||||
|
}
|
||||||
|
type Child = never;
|
||||||
|
declare class Popup extends View<Props, Child> {
|
||||||
|
timeout: Contract;
|
||||||
|
}
|
||||||
|
declare class PopupFactory extends ViewFactory<Props, Child, Popup> { }
|
||||||
|
declare const factory: PopupFactory;
|
||||||
|
export = factory;
|
||||||
@@ -18,9 +18,9 @@
|
|||||||
*
|
*
|
||||||
* # View props
|
* # View props
|
||||||
* - `header`: Text displayed at the top of the screen in bold
|
* - `header`: Text displayed at the top of the screen in bold
|
||||||
* - `items`: Array of selectable textual items
|
|
||||||
*
|
*
|
||||||
* @version Added in JS SDK 0.1
|
* @version Added in JS SDK 0.1
|
||||||
|
* @version API changed in JS SDK 0.4
|
||||||
* @module
|
* @module
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -29,9 +29,8 @@ import type { Contract } from "../event_loop";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
header: string,
|
header: string,
|
||||||
items: string[],
|
|
||||||
};
|
};
|
||||||
type Child = never;
|
type Child = string;
|
||||||
declare class Submenu extends View<Props, Child> {
|
declare class Submenu extends View<Props, Child> {
|
||||||
chosen: Contract<number>;
|
chosen: Contract<number>;
|
||||||
}
|
}
|
||||||
|
|||||||
38
applications/system/js_app/packages/fz-sdk/gui/vi_list.d.ts
vendored
Normal file
38
applications/system/js_app/packages/fz-sdk/gui/vi_list.d.ts
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Displays a list of settings-like variable items.
|
||||||
|
*
|
||||||
|
* <img src="../images/vi_list.png" width="200" alt="Sample screenshot of the view" />
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* let eventLoop = require("event_loop");
|
||||||
|
* let gui = require("gui");
|
||||||
|
* let viListView = require("gui/vi_list");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 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 doesn't have any props
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.4
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory } from ".";
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
|
||||||
|
type Props = {};
|
||||||
|
|
||||||
|
type Child = { label: string, variants: string[] };
|
||||||
|
|
||||||
|
declare class ViList extends View<Props, Child> {
|
||||||
|
valueUpdate: Contract<{ itemIndex: number, valueIndex: number }>;
|
||||||
|
}
|
||||||
|
declare class ViListFactory extends ViewFactory<Props, Child, ViList> { }
|
||||||
|
declare const factory: ViListFactory;
|
||||||
|
export = factory;
|
||||||
@@ -192,6 +192,9 @@ void lp5562_execute_ramp(
|
|||||||
// Prepare command sequence
|
// Prepare command sequence
|
||||||
uint16_t program[16];
|
uint16_t program[16];
|
||||||
uint8_t diff = (val_end > val_start) ? (val_end - val_start) : (val_start - val_end);
|
uint8_t diff = (val_end > val_start) ? (val_end - val_start) : (val_start - val_end);
|
||||||
|
if(diff == 0) { // Making division below safer
|
||||||
|
diff = 1;
|
||||||
|
}
|
||||||
uint16_t time_step = time * 2 / diff;
|
uint16_t time_step = time * 2 / diff;
|
||||||
uint8_t prescaller = 0;
|
uint8_t prescaller = 0;
|
||||||
if(time_step > 0x3F) {
|
if(time_step > 0x3F) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ typedef struct {
|
|||||||
uint8_t dots;
|
uint8_t dots;
|
||||||
} NoteBlock;
|
} NoteBlock;
|
||||||
|
|
||||||
ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST);
|
ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST); //-V658
|
||||||
|
|
||||||
struct MusicWorker {
|
struct MusicWorker {
|
||||||
FuriThread* thread;
|
FuriThread* thread;
|
||||||
|
|||||||
@@ -650,7 +650,7 @@ static int
|
|||||||
|
|
||||||
// evaluate flags
|
// evaluate flags
|
||||||
flags = 0U;
|
flags = 0U;
|
||||||
do {
|
do { //-V1044
|
||||||
switch(*format) {
|
switch(*format) {
|
||||||
case '0':
|
case '0':
|
||||||
flags |= FLAGS_ZEROPAD;
|
flags |= FLAGS_ZEROPAD;
|
||||||
|
|||||||
@@ -455,7 +455,7 @@ static bool
|
|||||||
|
|
||||||
//sort by number of occurrences
|
//sort by number of occurrences
|
||||||
bool swap = true;
|
bool swap = true;
|
||||||
while(swap) {
|
while(swap) { //-V1044
|
||||||
swap = false;
|
swap = false;
|
||||||
for(size_t i = 1; i < BIN_RAW_SEARCH_CLASSES; i++) {
|
for(size_t i = 1; i < BIN_RAW_SEARCH_CLASSES; i++) {
|
||||||
if(classes[i].count > classes[i - 1].count) {
|
if(classes[i].count > classes[i - 1].count) {
|
||||||
@@ -571,7 +571,7 @@ static bool
|
|||||||
bit_count = 0;
|
bit_count = 0;
|
||||||
|
|
||||||
if(data_markup_ind == BIN_RAW_MAX_MARKUP_COUNT) break;
|
if(data_markup_ind == BIN_RAW_MAX_MARKUP_COUNT) break;
|
||||||
ind &= 0xFFFFFFF8; //jump to the pre whole byte
|
ind &= 0xFFFFFFF8; //jump to the pre whole byte //-V784
|
||||||
}
|
}
|
||||||
} while(gap_ind != 0);
|
} while(gap_ind != 0);
|
||||||
if((data_markup_ind != BIN_RAW_MAX_MARKUP_COUNT) && (ind != 0)) {
|
if((data_markup_ind != BIN_RAW_MAX_MARKUP_COUNT) && (ind != 0)) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ typedef struct {
|
|||||||
SubGhzProtocolEncoderBase* base;
|
SubGhzProtocolEncoderBase* base;
|
||||||
} SubGhzReceiverSlot;
|
} SubGhzReceiverSlot;
|
||||||
|
|
||||||
ARRAY_DEF(SubGhzReceiverSlotArray, SubGhzReceiverSlot, M_POD_OPLIST);
|
ARRAY_DEF(SubGhzReceiverSlotArray, SubGhzReceiverSlot, M_POD_OPLIST); //-V658
|
||||||
#define M_OPL_SubGhzReceiverSlotArray_t() ARRAY_OPLIST(SubGhzReceiverSlotArray, M_POD_OPLIST)
|
#define M_OPL_SubGhzReceiverSlotArray_t() ARRAY_OPLIST(SubGhzReceiverSlotArray, M_POD_OPLIST)
|
||||||
|
|
||||||
struct SubGhzReceiver {
|
struct SubGhzReceiver {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ typedef struct {
|
|||||||
uint16_t type;
|
uint16_t type;
|
||||||
} SubGhzKey;
|
} SubGhzKey;
|
||||||
|
|
||||||
ARRAY_DEF(SubGhzKeyArray, SubGhzKey, M_POD_OPLIST)
|
ARRAY_DEF(SubGhzKeyArray, SubGhzKey, M_POD_OPLIST) //-V658
|
||||||
|
|
||||||
#define M_OPL_SubGhzKeyArray_t() ARRAY_OPLIST(SubGhzKeyArray, M_POD_OPLIST)
|
#define M_OPL_SubGhzKeyArray_t() ARRAY_OPLIST(SubGhzKeyArray, M_POD_OPLIST)
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ typedef struct {
|
|||||||
size_t custom_preset_data_size;
|
size_t custom_preset_data_size;
|
||||||
} SubGhzSettingCustomPresetItem;
|
} SubGhzSettingCustomPresetItem;
|
||||||
|
|
||||||
ARRAY_DEF(SubGhzSettingCustomPresetItemArray, SubGhzSettingCustomPresetItem, M_POD_OPLIST)
|
ARRAY_DEF(SubGhzSettingCustomPresetItemArray, SubGhzSettingCustomPresetItem, M_POD_OPLIST) //-V658
|
||||||
|
|
||||||
#define M_OPL_SubGhzSettingCustomPresetItemArray_t() \
|
#define M_OPL_SubGhzSettingCustomPresetItemArray_t() \
|
||||||
ARRAY_OPLIST(SubGhzSettingCustomPresetItemArray, M_POD_OPLIST)
|
ARRAY_OPLIST(SubGhzSettingCustomPresetItemArray, M_POD_OPLIST)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ env.Append(
|
|||||||
File("version.h"),
|
File("version.h"),
|
||||||
File("float_tools.h"),
|
File("float_tools.h"),
|
||||||
File("tar/tar_archive.h"),
|
File("tar/tar_archive.h"),
|
||||||
|
File("str_buffer.h"),
|
||||||
File("stream/stream.h"),
|
File("stream/stream.h"),
|
||||||
File("stream/file_stream.h"),
|
File("stream/file_stream.h"),
|
||||||
File("stream/string_stream.h"),
|
File("stream/string_stream.h"),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "cli_shell_completions.h"
|
#include "cli_shell_completions.h"
|
||||||
|
|
||||||
ARRAY_DEF(CommandCompletions, FuriString*, FURI_STRING_OPLIST); // -V524
|
ARRAY_DEF(CommandCompletions, FuriString*, FURI_STRING_OPLIST); // -V524 //-V658
|
||||||
#define M_OPL_CommandCompletions_t() ARRAY_OPLIST(CommandCompletions)
|
#define M_OPL_CommandCompletions_t() ARRAY_OPLIST(CommandCompletions)
|
||||||
|
|
||||||
struct CliShellCompletions {
|
struct CliShellCompletions {
|
||||||
|
|||||||
18
lib/toolbox/str_buffer.c
Normal file
18
lib/toolbox/str_buffer.c
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#include "str_buffer.h"
|
||||||
|
|
||||||
|
const char* str_buffer_make_owned_clone(StrBuffer* buffer, const char* str) {
|
||||||
|
char* owned = strdup(str);
|
||||||
|
buffer->n_owned_strings++;
|
||||||
|
buffer->owned_strings =
|
||||||
|
realloc(buffer->owned_strings, buffer->n_owned_strings * sizeof(const char*)); // -V701
|
||||||
|
buffer->owned_strings[buffer->n_owned_strings - 1] = owned;
|
||||||
|
return owned;
|
||||||
|
}
|
||||||
|
|
||||||
|
void str_buffer_clear_all_clones(StrBuffer* buffer) {
|
||||||
|
for(size_t i = 0; i < buffer->n_owned_strings; i++) {
|
||||||
|
free(buffer->owned_strings[i]);
|
||||||
|
}
|
||||||
|
free(buffer->owned_strings);
|
||||||
|
buffer->owned_strings = NULL;
|
||||||
|
}
|
||||||
47
lib/toolbox/str_buffer.h
Normal file
47
lib/toolbox/str_buffer.h
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* @file str_buffer.h
|
||||||
|
*
|
||||||
|
* Allows you to create an owned clone of however many strings that you need,
|
||||||
|
* then free all of them at once. Essentially the simplest possible append-only
|
||||||
|
* unindexable array of owned C-style strings.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief StrBuffer instance
|
||||||
|
*
|
||||||
|
* Place this struct directly wherever you want, it doesn't have to be `alloc`ed
|
||||||
|
* and `free`d.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
char** owned_strings;
|
||||||
|
size_t n_owned_strings;
|
||||||
|
} StrBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Makes a owned duplicate of the provided string
|
||||||
|
*
|
||||||
|
* @param[in] buffer StrBuffer instance
|
||||||
|
* @param[in] str Input C-style string
|
||||||
|
*
|
||||||
|
* @returns C-style string that contains to be valid event after `str` becomes
|
||||||
|
* invalid
|
||||||
|
*/
|
||||||
|
const char* str_buffer_make_owned_clone(StrBuffer* buffer, const char* str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clears all owned duplicates
|
||||||
|
*
|
||||||
|
* @param[in] buffer StrBuffer instance
|
||||||
|
*/
|
||||||
|
void str_buffer_clear_all_clones(StrBuffer* buffer);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -80,7 +80,7 @@ StrintParseError strint_to_uint64_internal(
|
|||||||
|
|
||||||
if(result > mul_limit) return StrintParseOverflowError;
|
if(result > mul_limit) return StrintParseOverflowError;
|
||||||
result *= base;
|
result *= base;
|
||||||
if(result > limit - digit_value) return StrintParseOverflowError;
|
if(result > limit - digit_value) return StrintParseOverflowError; //-V658
|
||||||
result += digit_value;
|
result += digit_value;
|
||||||
|
|
||||||
read_total++;
|
read_total++;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
entry,status,name,type,params
|
entry,status,name,type,params
|
||||||
Version,+,86.1,,
|
Version,+,87.0,,
|
||||||
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,,
|
||||||
@@ -173,6 +173,7 @@ Header,+,lib/toolbox/protocols/protocol_dict.h,,
|
|||||||
Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,,
|
Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,,
|
||||||
Header,+,lib/toolbox/saved_struct.h,,
|
Header,+,lib/toolbox/saved_struct.h,,
|
||||||
Header,+,lib/toolbox/simple_array.h,,
|
Header,+,lib/toolbox/simple_array.h,,
|
||||||
|
Header,+,lib/toolbox/str_buffer.h,,
|
||||||
Header,+,lib/toolbox/stream/buffered_file_stream.h,,
|
Header,+,lib/toolbox/stream/buffered_file_stream.h,,
|
||||||
Header,+,lib/toolbox/stream/file_stream.h,,
|
Header,+,lib/toolbox/stream/file_stream.h,,
|
||||||
Header,+,lib/toolbox/stream/stream.h,,
|
Header,+,lib/toolbox/stream/stream.h,,
|
||||||
@@ -703,6 +704,7 @@ Function,+,bt_forget_bonded_devices,void,Bt*
|
|||||||
Function,+,bt_keys_storage_alloc,BtKeysStorage*,const char*
|
Function,+,bt_keys_storage_alloc,BtKeysStorage*,const char*
|
||||||
Function,+,bt_keys_storage_delete,_Bool,BtKeysStorage*
|
Function,+,bt_keys_storage_delete,_Bool,BtKeysStorage*
|
||||||
Function,+,bt_keys_storage_free,void,BtKeysStorage*
|
Function,+,bt_keys_storage_free,void,BtKeysStorage*
|
||||||
|
Function,+,bt_keys_storage_get_root_keys,const GapRootSecurityKeys*,BtKeysStorage*
|
||||||
Function,+,bt_keys_storage_is_changed,_Bool,BtKeysStorage*
|
Function,+,bt_keys_storage_is_changed,_Bool,BtKeysStorage*
|
||||||
Function,+,bt_keys_storage_load,_Bool,BtKeysStorage*
|
Function,+,bt_keys_storage_load,_Bool,BtKeysStorage*
|
||||||
Function,+,bt_keys_storage_set_default_path,void,Bt*
|
Function,+,bt_keys_storage_set_default_path,void,Bt*
|
||||||
@@ -1173,7 +1175,7 @@ Function,+,furi_hal_adc_convert_vref,float,"FuriHalAdcHandle*, uint16_t"
|
|||||||
Function,+,furi_hal_adc_init,void,
|
Function,+,furi_hal_adc_init,void,
|
||||||
Function,+,furi_hal_adc_read,uint16_t,"FuriHalAdcHandle*, FuriHalAdcChannel"
|
Function,+,furi_hal_adc_read,uint16_t,"FuriHalAdcHandle*, FuriHalAdcChannel"
|
||||||
Function,+,furi_hal_adc_release,void,FuriHalAdcHandle*
|
Function,+,furi_hal_adc_release,void,FuriHalAdcHandle*
|
||||||
Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*"
|
Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, const GapRootSecurityKeys*, GapEventCallback, void*"
|
||||||
Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*"
|
Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*"
|
||||||
Function,+,furi_hal_bt_clear_white_list,_Bool,
|
Function,+,furi_hal_bt_clear_white_list,_Bool,
|
||||||
Function,+,furi_hal_bt_dump_state,void,FuriString*
|
Function,+,furi_hal_bt_dump_state,void,FuriString*
|
||||||
@@ -1200,7 +1202,7 @@ Function,+,furi_hal_bt_nvm_sram_sem_release,void,
|
|||||||
Function,+,furi_hal_bt_reinit,void,
|
Function,+,furi_hal_bt_reinit,void,
|
||||||
Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*"
|
Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*"
|
||||||
Function,+,furi_hal_bt_start_advertising,void,
|
Function,+,furi_hal_bt_start_advertising,void,
|
||||||
Function,+,furi_hal_bt_start_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*"
|
Function,+,furi_hal_bt_start_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, const GapRootSecurityKeys*, GapEventCallback, void*"
|
||||||
Function,+,furi_hal_bt_start_packet_rx,void,"uint8_t, uint8_t"
|
Function,+,furi_hal_bt_start_packet_rx,void,"uint8_t, uint8_t"
|
||||||
Function,+,furi_hal_bt_start_packet_tx,void,"uint8_t, uint8_t, uint8_t"
|
Function,+,furi_hal_bt_start_packet_tx,void,"uint8_t, uint8_t, uint8_t"
|
||||||
Function,+,furi_hal_bt_start_radio_stack,_Bool,
|
Function,+,furi_hal_bt_start_radio_stack,_Bool,
|
||||||
@@ -1740,7 +1742,7 @@ Function,-,gap_extra_beacon_set_data,_Bool,"const uint8_t*, uint8_t"
|
|||||||
Function,-,gap_extra_beacon_start,_Bool,
|
Function,-,gap_extra_beacon_start,_Bool,
|
||||||
Function,-,gap_extra_beacon_stop,_Bool,
|
Function,-,gap_extra_beacon_stop,_Bool,
|
||||||
Function,-,gap_get_state,GapState,
|
Function,-,gap_get_state,GapState,
|
||||||
Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*"
|
Function,-,gap_init,_Bool,"GapConfig*, const GapRootSecurityKeys*, GapEventCallback, void*"
|
||||||
Function,-,gap_start_advertising,void,
|
Function,-,gap_start_advertising,void,
|
||||||
Function,-,gap_stop_advertising,void,
|
Function,-,gap_stop_advertising,void,
|
||||||
Function,-,gap_thread_stop,void,
|
Function,-,gap_thread_stop,void,
|
||||||
@@ -2635,6 +2637,8 @@ Function,+,storage_simply_remove,_Bool,"Storage*, const char*"
|
|||||||
Function,+,storage_simply_remove_recursive,_Bool,"Storage*, const char*"
|
Function,+,storage_simply_remove_recursive,_Bool,"Storage*, const char*"
|
||||||
Function,-,stpcpy,char*,"char*, const char*"
|
Function,-,stpcpy,char*,"char*, const char*"
|
||||||
Function,-,stpncpy,char*,"char*, const char*, size_t"
|
Function,-,stpncpy,char*,"char*, const char*, size_t"
|
||||||
|
Function,+,str_buffer_clear_all_clones,void,StrBuffer*
|
||||||
|
Function,+,str_buffer_make_owned_clone,const char*,"StrBuffer*, const char*"
|
||||||
Function,+,strcasecmp,int,"const char*, const char*"
|
Function,+,strcasecmp,int,"const char*, const char*"
|
||||||
Function,-,strcasecmp_l,int,"const char*, const char*, locale_t"
|
Function,-,strcasecmp_l,int,"const char*, const char*, locale_t"
|
||||||
Function,+,strcasestr,char*,"const char*, const char*"
|
Function,+,strcasestr,char*,"const char*, const char*"
|
||||||
|
|||||||
|
@@ -1,5 +1,5 @@
|
|||||||
entry,status,name,type,params
|
entry,status,name,type,params
|
||||||
Version,+,86.1,,
|
Version,+,87.0,,
|
||||||
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,,
|
||||||
@@ -257,6 +257,7 @@ Header,+,lib/toolbox/protocols/protocol_dict.h,,
|
|||||||
Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,,
|
Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,,
|
||||||
Header,+,lib/toolbox/saved_struct.h,,
|
Header,+,lib/toolbox/saved_struct.h,,
|
||||||
Header,+,lib/toolbox/simple_array.h,,
|
Header,+,lib/toolbox/simple_array.h,,
|
||||||
|
Header,+,lib/toolbox/str_buffer.h,,
|
||||||
Header,+,lib/toolbox/stream/buffered_file_stream.h,,
|
Header,+,lib/toolbox/stream/buffered_file_stream.h,,
|
||||||
Header,+,lib/toolbox/stream/file_stream.h,,
|
Header,+,lib/toolbox/stream/file_stream.h,,
|
||||||
Header,+,lib/toolbox/stream/stream.h,,
|
Header,+,lib/toolbox/stream/stream.h,,
|
||||||
@@ -792,6 +793,7 @@ Function,+,bt_forget_bonded_devices,void,Bt*
|
|||||||
Function,+,bt_keys_storage_alloc,BtKeysStorage*,const char*
|
Function,+,bt_keys_storage_alloc,BtKeysStorage*,const char*
|
||||||
Function,+,bt_keys_storage_delete,_Bool,BtKeysStorage*
|
Function,+,bt_keys_storage_delete,_Bool,BtKeysStorage*
|
||||||
Function,+,bt_keys_storage_free,void,BtKeysStorage*
|
Function,+,bt_keys_storage_free,void,BtKeysStorage*
|
||||||
|
Function,+,bt_keys_storage_get_root_keys,const GapRootSecurityKeys*,BtKeysStorage*
|
||||||
Function,+,bt_keys_storage_is_changed,_Bool,BtKeysStorage*
|
Function,+,bt_keys_storage_is_changed,_Bool,BtKeysStorage*
|
||||||
Function,+,bt_keys_storage_load,_Bool,BtKeysStorage*
|
Function,+,bt_keys_storage_load,_Bool,BtKeysStorage*
|
||||||
Function,+,bt_keys_storage_set_default_path,void,Bt*
|
Function,+,bt_keys_storage_set_default_path,void,Bt*
|
||||||
@@ -1322,7 +1324,7 @@ Function,+,furi_hal_adc_convert_vref,float,"FuriHalAdcHandle*, uint16_t"
|
|||||||
Function,+,furi_hal_adc_init,void,
|
Function,+,furi_hal_adc_init,void,
|
||||||
Function,+,furi_hal_adc_read,uint16_t,"FuriHalAdcHandle*, FuriHalAdcChannel"
|
Function,+,furi_hal_adc_read,uint16_t,"FuriHalAdcHandle*, FuriHalAdcChannel"
|
||||||
Function,+,furi_hal_adc_release,void,FuriHalAdcHandle*
|
Function,+,furi_hal_adc_release,void,FuriHalAdcHandle*
|
||||||
Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*"
|
Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, const GapRootSecurityKeys*, GapEventCallback, void*"
|
||||||
Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*"
|
Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*"
|
||||||
Function,+,furi_hal_bt_clear_white_list,_Bool,
|
Function,+,furi_hal_bt_clear_white_list,_Bool,
|
||||||
Function,+,furi_hal_bt_dump_state,void,FuriString*
|
Function,+,furi_hal_bt_dump_state,void,FuriString*
|
||||||
@@ -1349,7 +1351,7 @@ Function,+,furi_hal_bt_nvm_sram_sem_release,void,
|
|||||||
Function,+,furi_hal_bt_reinit,void,
|
Function,+,furi_hal_bt_reinit,void,
|
||||||
Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*"
|
Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*"
|
||||||
Function,+,furi_hal_bt_start_advertising,void,
|
Function,+,furi_hal_bt_start_advertising,void,
|
||||||
Function,+,furi_hal_bt_start_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*"
|
Function,+,furi_hal_bt_start_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, const GapRootSecurityKeys*, GapEventCallback, void*"
|
||||||
Function,+,furi_hal_bt_start_packet_rx,void,"uint8_t, uint8_t"
|
Function,+,furi_hal_bt_start_packet_rx,void,"uint8_t, uint8_t"
|
||||||
Function,+,furi_hal_bt_start_packet_tx,void,"uint8_t, uint8_t, uint8_t"
|
Function,+,furi_hal_bt_start_packet_tx,void,"uint8_t, uint8_t, uint8_t"
|
||||||
Function,+,furi_hal_bt_start_radio_stack,_Bool,
|
Function,+,furi_hal_bt_start_radio_stack,_Bool,
|
||||||
@@ -2009,7 +2011,7 @@ Function,-,gap_extra_beacon_set_data,_Bool,"const uint8_t*, uint8_t"
|
|||||||
Function,-,gap_extra_beacon_start,_Bool,
|
Function,-,gap_extra_beacon_start,_Bool,
|
||||||
Function,-,gap_extra_beacon_stop,_Bool,
|
Function,-,gap_extra_beacon_stop,_Bool,
|
||||||
Function,-,gap_get_state,GapState,
|
Function,-,gap_get_state,GapState,
|
||||||
Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*"
|
Function,-,gap_init,_Bool,"GapConfig*, const GapRootSecurityKeys*, GapEventCallback, void*"
|
||||||
Function,-,gap_start_advertising,void,
|
Function,-,gap_start_advertising,void,
|
||||||
Function,-,gap_stop_advertising,void,
|
Function,-,gap_stop_advertising,void,
|
||||||
Function,-,gap_thread_stop,void,
|
Function,-,gap_thread_stop,void,
|
||||||
@@ -3413,6 +3415,8 @@ Function,+,storage_simply_remove,_Bool,"Storage*, const char*"
|
|||||||
Function,+,storage_simply_remove_recursive,_Bool,"Storage*, const char*"
|
Function,+,storage_simply_remove_recursive,_Bool,"Storage*, const char*"
|
||||||
Function,-,stpcpy,char*,"char*, const char*"
|
Function,-,stpcpy,char*,"char*, const char*"
|
||||||
Function,-,stpncpy,char*,"char*, const char*, size_t"
|
Function,-,stpncpy,char*,"char*, const char*, size_t"
|
||||||
|
Function,+,str_buffer_clear_all_clones,void,StrBuffer*
|
||||||
|
Function,+,str_buffer_make_owned_clone,const char*,"StrBuffer*, const char*"
|
||||||
Function,+,strcasecmp,int,"const char*, const char*"
|
Function,+,strcasecmp,int,"const char*, const char*"
|
||||||
Function,-,strcasecmp_l,int,"const char*, const char*, locale_t"
|
Function,-,strcasecmp_l,int,"const char*, const char*, locale_t"
|
||||||
Function,+,strcasestr,char*,"const char*, const char*"
|
Function,+,strcasestr,char*,"const char*, const char*"
|
||||||
|
|||||||
|
@@ -51,13 +51,6 @@ typedef enum {
|
|||||||
GapCommandKillThread,
|
GapCommandKillThread,
|
||||||
} GapCommand;
|
} GapCommand;
|
||||||
|
|
||||||
// Identity root key
|
|
||||||
static const uint8_t gap_irk[16] =
|
|
||||||
{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0};
|
|
||||||
// Encryption root key
|
|
||||||
static const uint8_t gap_erk[16] =
|
|
||||||
{0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21};
|
|
||||||
|
|
||||||
static Gap* gap = NULL;
|
static Gap* gap = NULL;
|
||||||
|
|
||||||
static void gap_advertise_start(GapState new_state);
|
static void gap_advertise_start(GapState new_state);
|
||||||
@@ -335,7 +328,9 @@ static void set_manufacturer_data(uint8_t* mfg_data, uint8_t mfg_data_len) {
|
|||||||
gap->service.mfg_data_len += mfg_data_len;
|
gap->service.mfg_data_len += mfg_data_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gap_init_svc(Gap* gap) {
|
static void gap_init_svc(Gap* gap, const GapRootSecurityKeys* root_keys) {
|
||||||
|
furi_check(root_keys);
|
||||||
|
|
||||||
tBleStatus status;
|
tBleStatus status;
|
||||||
uint32_t srd_bd_addr[2];
|
uint32_t srd_bd_addr[2];
|
||||||
|
|
||||||
@@ -353,9 +348,9 @@ static void gap_init_svc(Gap* gap) {
|
|||||||
aci_hal_write_config_data(
|
aci_hal_write_config_data(
|
||||||
CONFIG_DATA_RANDOM_ADDRESS_OFFSET, CONFIG_DATA_RANDOM_ADDRESS_LEN, (uint8_t*)srd_bd_addr);
|
CONFIG_DATA_RANDOM_ADDRESS_OFFSET, CONFIG_DATA_RANDOM_ADDRESS_LEN, (uint8_t*)srd_bd_addr);
|
||||||
// Set Identity root key used to derive LTK and CSRK
|
// Set Identity root key used to derive LTK and CSRK
|
||||||
aci_hal_write_config_data(CONFIG_DATA_IR_OFFSET, CONFIG_DATA_IR_LEN, (uint8_t*)gap_irk);
|
aci_hal_write_config_data(CONFIG_DATA_IR_OFFSET, CONFIG_DATA_IR_LEN, root_keys->irk);
|
||||||
// Set Encryption root key used to derive LTK and CSRK
|
// Set Encryption root key used to derive LTK and CSRK
|
||||||
aci_hal_write_config_data(CONFIG_DATA_ER_OFFSET, CONFIG_DATA_ER_LEN, (uint8_t*)gap_erk);
|
aci_hal_write_config_data(CONFIG_DATA_ER_OFFSET, CONFIG_DATA_ER_LEN, root_keys->erk);
|
||||||
// Set TX Power to 0 dBm
|
// Set TX Power to 0 dBm
|
||||||
aci_hal_set_tx_power_level(1, 0x19);
|
aci_hal_set_tx_power_level(1, 0x19);
|
||||||
// Initialize GATT interface
|
// Initialize GATT interface
|
||||||
@@ -537,7 +532,11 @@ static void gap_advertise_timer_callback(void* context) {
|
|||||||
furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk);
|
furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) {
|
bool gap_init(
|
||||||
|
GapConfig* config,
|
||||||
|
const GapRootSecurityKeys* root_keys,
|
||||||
|
GapEventCallback on_event_cb,
|
||||||
|
void* context) {
|
||||||
if(!ble_glue_is_radio_stack_ready()) {
|
if(!ble_glue_is_radio_stack_ready()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -550,7 +549,7 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) {
|
|||||||
gap->advertise_timer = furi_timer_alloc(gap_advertise_timer_callback, FuriTimerTypeOnce, NULL);
|
gap->advertise_timer = furi_timer_alloc(gap_advertise_timer_callback, FuriTimerTypeOnce, NULL);
|
||||||
// Initialization of GATT & GAP layer
|
// Initialization of GATT & GAP layer
|
||||||
gap->service.adv_name = config->adv_name;
|
gap->service.adv_name = config->adv_name;
|
||||||
gap_init_svc(gap);
|
gap_init_svc(gap, root_keys);
|
||||||
ble_event_dispatcher_init();
|
ble_event_dispatcher_init();
|
||||||
// Initialization of the GAP state
|
// Initialization of the GAP state
|
||||||
gap->state_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
gap->state_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||||
@@ -575,14 +574,13 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) {
|
|||||||
set_manufacturer_data(gap->config->mfg_data, gap->config->mfg_data_len);
|
set_manufacturer_data(gap->config->mfg_data, gap->config->mfg_data_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gap->service.adv_svc_uuid_len = 1;
|
||||||
if(gap->config->adv_service.UUID_Type == UUID_TYPE_16) {
|
if(gap->config->adv_service.UUID_Type == UUID_TYPE_16) {
|
||||||
uint8_t adv_service_uid[2];
|
uint8_t adv_service_uid[2];
|
||||||
gap->service.adv_svc_uuid_len = 1;
|
|
||||||
adv_service_uid[0] = gap->config->adv_service.Service_UUID_16 & 0xff;
|
adv_service_uid[0] = gap->config->adv_service.Service_UUID_16 & 0xff;
|
||||||
adv_service_uid[1] = gap->config->adv_service.Service_UUID_16 >> 8;
|
adv_service_uid[1] = gap->config->adv_service.Service_UUID_16 >> 8;
|
||||||
set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid));
|
set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid));
|
||||||
} else if(gap->config->adv_service.UUID_Type == UUID_TYPE_128) {
|
} else if(gap->config->adv_service.UUID_Type == UUID_TYPE_128) {
|
||||||
gap->service.adv_svc_uuid_len = 1;
|
|
||||||
set_advertisment_service_uid(
|
set_advertisment_service_uid(
|
||||||
gap->config->adv_service.Service_UUID_128,
|
gap->config->adv_service.Service_UUID_128,
|
||||||
sizeof(gap->config->adv_service.Service_UUID_128));
|
sizeof(gap->config->adv_service.Service_UUID_128));
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <furi_hal_version.h>
|
#include <furi_hal_version.h>
|
||||||
|
|
||||||
#define GAP_MAC_ADDR_SIZE (6)
|
#define GAP_MAC_ADDR_SIZE (6)
|
||||||
|
#define GAP_KEY_SIZE (0x10)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* GAP helpers - background thread that handles BLE GAP events and advertising.
|
* GAP helpers - background thread that handles BLE GAP events and advertising.
|
||||||
@@ -84,7 +85,18 @@ typedef struct {
|
|||||||
GapConnectionParamsRequest conn_param;
|
GapConnectionParamsRequest conn_param;
|
||||||
} GapConfig;
|
} GapConfig;
|
||||||
|
|
||||||
bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context);
|
typedef struct {
|
||||||
|
// Encryption Root key. Must be unique per-device (or app)
|
||||||
|
uint8_t erk[GAP_KEY_SIZE];
|
||||||
|
// Identity Root key. Used for resolving RPAs, if configured
|
||||||
|
uint8_t irk[GAP_KEY_SIZE];
|
||||||
|
} GapRootSecurityKeys;
|
||||||
|
|
||||||
|
bool gap_init(
|
||||||
|
GapConfig* config,
|
||||||
|
const GapRootSecurityKeys* root_keys,
|
||||||
|
GapEventCallback on_event_cb,
|
||||||
|
void* context);
|
||||||
|
|
||||||
void gap_start_advertising(void);
|
void gap_start_advertising(void);
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ static FuriHalBt furi_hal_bt = {
|
|||||||
.stack = FuriHalBtStackUnknown,
|
.stack = FuriHalBtStackUnknown,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static FuriHalBleProfileBase* current_profile = NULL;
|
||||||
|
static GapConfig current_config = {0};
|
||||||
|
|
||||||
void furi_hal_bt_init(void) {
|
void furi_hal_bt_init(void) {
|
||||||
FURI_LOG_I(TAG, "Start BT initialization");
|
FURI_LOG_I(TAG, "Start BT initialization");
|
||||||
furi_hal_bus_enable(FuriHalBusHSEM);
|
furi_hal_bus_enable(FuriHalBusHSEM);
|
||||||
@@ -149,9 +152,6 @@ bool furi_hal_bt_is_testing_supported(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static FuriHalBleProfileBase* current_profile = NULL;
|
|
||||||
static GapConfig current_config = {0};
|
|
||||||
|
|
||||||
bool furi_hal_bt_check_profile_type(
|
bool furi_hal_bt_check_profile_type(
|
||||||
FuriHalBleProfileBase* profile,
|
FuriHalBleProfileBase* profile,
|
||||||
const FuriHalBleProfileTemplate* profile_template) {
|
const FuriHalBleProfileTemplate* profile_template) {
|
||||||
@@ -165,10 +165,12 @@ bool furi_hal_bt_check_profile_type(
|
|||||||
FuriHalBleProfileBase* furi_hal_bt_start_app(
|
FuriHalBleProfileBase* furi_hal_bt_start_app(
|
||||||
const FuriHalBleProfileTemplate* profile_template,
|
const FuriHalBleProfileTemplate* profile_template,
|
||||||
FuriHalBleProfileParams params,
|
FuriHalBleProfileParams params,
|
||||||
|
const GapRootSecurityKeys* root_keys,
|
||||||
GapEventCallback event_cb,
|
GapEventCallback event_cb,
|
||||||
void* context) {
|
void* context) {
|
||||||
furi_check(event_cb);
|
furi_check(event_cb);
|
||||||
furi_check(profile_template);
|
furi_check(profile_template);
|
||||||
|
furi_check(root_keys);
|
||||||
furi_check(current_profile == NULL);
|
furi_check(current_profile == NULL);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
@@ -183,7 +185,7 @@ FuriHalBleProfileBase* furi_hal_bt_start_app(
|
|||||||
|
|
||||||
profile_template->get_gap_config(¤t_config, params);
|
profile_template->get_gap_config(¤t_config, params);
|
||||||
|
|
||||||
if(!gap_init(¤t_config, event_cb, context)) {
|
if(!gap_init(¤t_config, root_keys, event_cb, context)) {
|
||||||
gap_thread_stop();
|
gap_thread_stop();
|
||||||
FURI_LOG_E(TAG, "Failed to init GAP");
|
FURI_LOG_E(TAG, "Failed to init GAP");
|
||||||
break;
|
break;
|
||||||
@@ -239,12 +241,11 @@ void furi_hal_bt_reinit(void) {
|
|||||||
FuriHalBleProfileBase* furi_hal_bt_change_app(
|
FuriHalBleProfileBase* furi_hal_bt_change_app(
|
||||||
const FuriHalBleProfileTemplate* profile_template,
|
const FuriHalBleProfileTemplate* profile_template,
|
||||||
FuriHalBleProfileParams profile_params,
|
FuriHalBleProfileParams profile_params,
|
||||||
|
const GapRootSecurityKeys* root_keys,
|
||||||
GapEventCallback event_cb,
|
GapEventCallback event_cb,
|
||||||
void* context) {
|
void* context) {
|
||||||
furi_check(event_cb);
|
|
||||||
|
|
||||||
furi_hal_bt_reinit();
|
furi_hal_bt_reinit();
|
||||||
return furi_hal_bt_start_app(profile_template, profile_params, event_cb, context);
|
return furi_hal_bt_start_app(profile_template, profile_params, root_keys, event_cb, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool furi_hal_bt_is_active(void) {
|
bool furi_hal_bt_is_active(void) {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ void SystemInit(void) {
|
|||||||
RCC->PLLSAI1CFGR = 0x22041000U;
|
RCC->PLLSAI1CFGR = 0x22041000U;
|
||||||
#endif
|
#endif
|
||||||
// Reset HSEBYP bit
|
// Reset HSEBYP bit
|
||||||
RCC->CR &= 0xFFFBFFFFU;
|
RCC->CR &= 0xFFFBFFFFU; //-V784
|
||||||
// Disable all RCC related interrupts
|
// Disable all RCC related interrupts
|
||||||
RCC->CIER = 0x00000000;
|
RCC->CIER = 0x00000000;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ bool furi_hal_bt_check_profile_type(
|
|||||||
*
|
*
|
||||||
* @param profile_template FuriHalBleProfileTemplate instance
|
* @param profile_template FuriHalBleProfileTemplate instance
|
||||||
* @param params Parameters to pass to the profile. Can be NULL
|
* @param params Parameters to pass to the profile. Can be NULL
|
||||||
|
* @param root_keys pointer to root keys
|
||||||
* @param event_cb GapEventCallback instance
|
* @param event_cb GapEventCallback instance
|
||||||
* @param context pointer to context
|
* @param context pointer to context
|
||||||
*
|
*
|
||||||
@@ -85,6 +86,7 @@ bool furi_hal_bt_check_profile_type(
|
|||||||
FURI_WARN_UNUSED FuriHalBleProfileBase* furi_hal_bt_start_app(
|
FURI_WARN_UNUSED FuriHalBleProfileBase* furi_hal_bt_start_app(
|
||||||
const FuriHalBleProfileTemplate* profile_template,
|
const FuriHalBleProfileTemplate* profile_template,
|
||||||
FuriHalBleProfileParams params,
|
FuriHalBleProfileParams params,
|
||||||
|
const GapRootSecurityKeys* root_keys,
|
||||||
GapEventCallback event_cb,
|
GapEventCallback event_cb,
|
||||||
void* context);
|
void* context);
|
||||||
|
|
||||||
@@ -100,6 +102,7 @@ void furi_hal_bt_reinit(void);
|
|||||||
* @param profile_template FuriHalBleProfileTemplate instance
|
* @param profile_template FuriHalBleProfileTemplate instance
|
||||||
* @param profile_params Parameters to pass to the profile. Can be NULL
|
* @param profile_params Parameters to pass to the profile. Can be NULL
|
||||||
* @param event_cb GapEventCallback instance
|
* @param event_cb GapEventCallback instance
|
||||||
|
* @param root_keys pointer to root keys
|
||||||
* @param context pointer to context
|
* @param context pointer to context
|
||||||
*
|
*
|
||||||
* @return instance of profile, NULL on failure
|
* @return instance of profile, NULL on failure
|
||||||
@@ -107,6 +110,7 @@ void furi_hal_bt_reinit(void);
|
|||||||
FURI_WARN_UNUSED FuriHalBleProfileBase* furi_hal_bt_change_app(
|
FURI_WARN_UNUSED FuriHalBleProfileBase* furi_hal_bt_change_app(
|
||||||
const FuriHalBleProfileTemplate* profile_template,
|
const FuriHalBleProfileTemplate* profile_template,
|
||||||
FuriHalBleProfileParams profile_params,
|
FuriHalBleProfileParams profile_params,
|
||||||
|
const GapRootSecurityKeys* root_keys,
|
||||||
GapEventCallback event_cb,
|
GapEventCallback event_cb,
|
||||||
void* context);
|
void* context);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user