mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-12 18:58:36 -07:00
Merge branch 'dev' into astra/3746-mfp-detect
This commit is contained in:
@@ -13,6 +13,12 @@
|
||||
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller.h>
|
||||
#include <nfc/protocols/iso15693_3/iso15693_3_poller.h>
|
||||
#include <nfc/protocols/slix/slix.h>
|
||||
#include <nfc/protocols/slix/slix_i.h>
|
||||
#include <nfc/protocols/slix/slix_poller.h>
|
||||
#include <nfc/protocols/slix/slix_poller_i.h>
|
||||
|
||||
#include <nfc/nfc_poller.h>
|
||||
|
||||
#include <toolbox/keys_dict.h>
|
||||
@@ -42,6 +48,19 @@ typedef struct {
|
||||
FuriThreadId thread_id;
|
||||
} NfcTestMfClassicSendFrameTest;
|
||||
|
||||
typedef enum {
|
||||
NfcTestSlixPollerSetPasswordStateGetRandomNumber,
|
||||
NfcTestSlixPollerSetPasswordStateSetPassword,
|
||||
} NfcTestSlixPollerSetPasswordState;
|
||||
|
||||
typedef struct {
|
||||
FuriThreadId thread_id;
|
||||
NfcTestSlixPollerSetPasswordState state;
|
||||
SlixRandomNumber random_number;
|
||||
SlixPassword password;
|
||||
SlixError error;
|
||||
} NfcTestSlixPollerSetPasswordContext;
|
||||
|
||||
typedef struct {
|
||||
Storage* storage;
|
||||
} NfcTest;
|
||||
@@ -627,6 +646,127 @@ MU_TEST(mf_classic_dict_test) {
|
||||
"Remove test dict failed");
|
||||
}
|
||||
|
||||
MU_TEST(slix_file_with_capabilities_test) {
|
||||
NfcDevice* nfc_device_missed_cap = nfc_device_alloc();
|
||||
mu_assert(
|
||||
nfc_device_load(nfc_device_missed_cap, EXT_PATH("unit_tests/nfc/Slix_cap_missed.nfc")),
|
||||
"nfc_device_load() failed\r\n");
|
||||
|
||||
NfcDevice* nfc_device_default_cap = nfc_device_alloc();
|
||||
mu_assert(
|
||||
nfc_device_load(nfc_device_default_cap, EXT_PATH("unit_tests/nfc/Slix_cap_default.nfc")),
|
||||
"nfc_device_load() failed\r\n");
|
||||
|
||||
mu_assert(
|
||||
nfc_device_is_equal(nfc_device_missed_cap, nfc_device_default_cap),
|
||||
"nfc_device_is_equal() failed\r\n");
|
||||
|
||||
nfc_device_free(nfc_device_default_cap);
|
||||
nfc_device_free(nfc_device_missed_cap);
|
||||
}
|
||||
|
||||
NfcCommand slix_poller_set_password_callback(NfcGenericEventEx event, void* context) {
|
||||
furi_check(event.poller);
|
||||
furi_check(event.parent_event_data);
|
||||
furi_check(context);
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
Iso15693_3PollerEvent* iso15_event = event.parent_event_data;
|
||||
SlixPoller* poller = event.poller;
|
||||
NfcTestSlixPollerSetPasswordContext* slix_ctx = context;
|
||||
|
||||
if(iso15_event->type == Iso15693_3PollerEventTypeReady) {
|
||||
iso15693_3_copy(
|
||||
poller->data->iso15693_3_data, iso15693_3_poller_get_data(poller->iso15693_3_poller));
|
||||
|
||||
if(slix_ctx->state == NfcTestSlixPollerSetPasswordStateGetRandomNumber) {
|
||||
slix_ctx->error = slix_poller_get_random_number(poller, &slix_ctx->random_number);
|
||||
if(slix_ctx->error != SlixErrorNone) {
|
||||
furi_thread_flags_set(slix_ctx->thread_id, NFC_TEST_FLAG_WORKER_DONE);
|
||||
command = NfcCommandStop;
|
||||
} else {
|
||||
slix_ctx->state = NfcTestSlixPollerSetPasswordStateSetPassword;
|
||||
}
|
||||
} else if(slix_ctx->state == NfcTestSlixPollerSetPasswordStateSetPassword) {
|
||||
slix_ctx->error = slix_poller_set_password(
|
||||
poller, SlixPasswordTypeRead, slix_ctx->password, slix_ctx->random_number);
|
||||
furi_thread_flags_set(slix_ctx->thread_id, NFC_TEST_FLAG_WORKER_DONE);
|
||||
command = NfcCommandStop;
|
||||
}
|
||||
} else {
|
||||
slix_ctx->error = slix_process_iso15693_3_error(iso15_event->data->error);
|
||||
furi_thread_flags_set(slix_ctx->thread_id, NFC_TEST_FLAG_WORKER_DONE);
|
||||
command = NfcCommandStop;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static void slix_set_password_test(const char* file_path, SlixPassword pass, bool correct_pass) {
|
||||
FURI_LOG_I(TAG, "Testing file: %s", file_path);
|
||||
|
||||
Nfc* poller = nfc_alloc();
|
||||
Nfc* listener = nfc_alloc();
|
||||
|
||||
NfcDevice* nfc_device = nfc_device_alloc();
|
||||
mu_assert(nfc_device_load(nfc_device, file_path), "nfc_device_load() failed\r\n");
|
||||
|
||||
const SlixData* slix_data = nfc_device_get_data(nfc_device, NfcProtocolSlix);
|
||||
NfcListener* slix_listener = nfc_listener_alloc(listener, NfcProtocolSlix, slix_data);
|
||||
nfc_listener_start(slix_listener, NULL, NULL);
|
||||
|
||||
SlixCapabilities slix_capabilities = slix_data->capabilities;
|
||||
|
||||
NfcPoller* slix_poller = nfc_poller_alloc(poller, NfcProtocolSlix);
|
||||
|
||||
NfcTestSlixPollerSetPasswordContext slix_poller_context = {
|
||||
.thread_id = furi_thread_get_current_id(),
|
||||
.state = NfcTestSlixPollerSetPasswordStateGetRandomNumber,
|
||||
.password = pass,
|
||||
.error = SlixErrorNone,
|
||||
};
|
||||
|
||||
nfc_poller_start_ex(slix_poller, slix_poller_set_password_callback, &slix_poller_context);
|
||||
|
||||
uint32_t flag =
|
||||
furi_thread_flags_wait(NFC_TEST_FLAG_WORKER_DONE, FuriFlagWaitAny, FuriWaitForever);
|
||||
mu_assert(flag == NFC_TEST_FLAG_WORKER_DONE, "Wrong thread flag\r\n");
|
||||
|
||||
nfc_poller_stop(slix_poller);
|
||||
nfc_poller_free(slix_poller);
|
||||
nfc_listener_stop(slix_listener);
|
||||
nfc_listener_free(slix_listener);
|
||||
|
||||
mu_assert(
|
||||
slix_poller_context.state == NfcTestSlixPollerSetPasswordStateSetPassword,
|
||||
"Poller failed before setting password\r\n");
|
||||
|
||||
if((slix_capabilities == SlixCapabilitiesAcceptAllPasswords) || (correct_pass)) {
|
||||
mu_assert(slix_poller_context.error == SlixErrorNone, "Failed to set password\r\n");
|
||||
} else {
|
||||
mu_assert(
|
||||
slix_poller_context.error == SlixErrorTimeout,
|
||||
"Must have received SlixErrorTimeout\r\n");
|
||||
}
|
||||
|
||||
nfc_device_free(nfc_device);
|
||||
nfc_free(listener);
|
||||
nfc_free(poller);
|
||||
}
|
||||
|
||||
MU_TEST(slix_set_password_default_cap_correct_pass) {
|
||||
slix_set_password_test(EXT_PATH("unit_tests/nfc/Slix_cap_default.nfc"), 0x00000000, true);
|
||||
}
|
||||
|
||||
MU_TEST(slix_set_password_default_cap_incorrect_pass) {
|
||||
slix_set_password_test(EXT_PATH("unit_tests/nfc/Slix_cap_default.nfc"), 0x12341234, false);
|
||||
}
|
||||
|
||||
MU_TEST(slix_set_password_access_all_passwords_cap) {
|
||||
slix_set_password_test(
|
||||
EXT_PATH("unit_tests/nfc/Slix_cap_accept_all_pass.nfc"), 0x12341234, false);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(nfc) {
|
||||
nfc_test_alloc();
|
||||
|
||||
@@ -668,6 +808,11 @@ MU_TEST_SUITE(nfc) {
|
||||
MU_RUN_TEST(mf_classic_send_frame_test);
|
||||
MU_RUN_TEST(mf_classic_dict_test);
|
||||
|
||||
MU_RUN_TEST(slix_file_with_capabilities_test);
|
||||
MU_RUN_TEST(slix_set_password_default_cap_correct_pass);
|
||||
MU_RUN_TEST(slix_set_password_default_cap_incorrect_pass);
|
||||
MU_RUN_TEST(slix_set_password_access_all_passwords_cap);
|
||||
|
||||
nfc_test_free();
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ struct Nfc {
|
||||
|
||||
Iso14443_3aColResStatus col_res_status;
|
||||
Iso14443_3aColResData col_res_data;
|
||||
bool software_col_res_required;
|
||||
|
||||
NfcEventCallback callback;
|
||||
void* context;
|
||||
@@ -170,6 +171,7 @@ NfcError nfc_iso14443a_listener_set_col_res_data(
|
||||
furi_check(atqa);
|
||||
|
||||
nfc_prepare_col_res_data(instance, uid, uid_len, atqa, sak);
|
||||
instance->software_col_res_required = true;
|
||||
|
||||
return NfcErrorNone;
|
||||
}
|
||||
@@ -275,7 +277,8 @@ static int32_t nfc_worker_listener(void* context) {
|
||||
} else if(message.type == NfcMessageTypeTx) {
|
||||
nfc_test_print(
|
||||
NfcTransportLogLevelInfo, "RDR", message.data.data, message.data.data_bits);
|
||||
if(instance->col_res_status != Iso14443_3aColResStatusDone) {
|
||||
if(instance->software_col_res_required &&
|
||||
(instance->col_res_status != Iso14443_3aColResStatusDone)) {
|
||||
nfc_worker_listener_pass_col_res(
|
||||
instance, message.data.data, message.data.data_bits);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
Filetype: Flipper NFC device
|
||||
Version: 4
|
||||
# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB
|
||||
Device type: SLIX
|
||||
# UID is common for all formats
|
||||
UID: E0 04 01 08 49 D0 DC 81
|
||||
# ISO15693-3 specific data
|
||||
# Data Storage Format Identifier
|
||||
DSFID: 01
|
||||
# Application Family Identifier
|
||||
AFI: 3D
|
||||
# IC Reference - Vendor specific meaning
|
||||
IC Reference: 01
|
||||
# Lock Bits
|
||||
Lock DSFID: true
|
||||
Lock AFI: true
|
||||
# Number of memory blocks, valid range = 1..256
|
||||
Block Count: 80
|
||||
# Size of a single memory block, valid range = 01...20 (hex)
|
||||
Block Size: 04
|
||||
Data Content: 03 0A 82 ED 86 39 61 D2 03 14 1E 32 B6 CA 00 3C 36 42 0C 33 53 30 37 32 32 34 30 30 00 00 00 00 00 FF 04 01 01 00 00 00 A3 03 1E 00 26 00 00 00 00 00 0F 00 76 03 65 01 00 00 00 00 85 01 34 00 75 09 05 00 01 00 00 00 00 00 00 00 00 00 00 00 D7 FA 00 1C 9E 1C 67 27 00 30 30 30 30 30 30 30 30 30 30 00 00 00 97 25 55 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 8C 00 30 53 48 80 DE 00 00 00 00 F4 C3 58 2B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 F3 00 2C DD C3 3E 91 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E5 FF 00 01
|
||||
# Block Security Status: 01 = locked, 00 = not locked
|
||||
Security Status: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
# SLIX specific data
|
||||
# SLIX capabilities field affects emulation modes. Possible options: Default, AcceptAllPasswords
|
||||
Capabilities: AcceptAllPasswords
|
||||
# Passwords are optional. If a password is omitted, a default value will be used
|
||||
Password Read: 00 00 00 00
|
||||
Password Write: 00 00 00 00
|
||||
Password Privacy: 0F 0F 0F 0F
|
||||
Password Destroy: 0F 0F 0F 0F
|
||||
Password EAS: 00 00 00 00
|
||||
# This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key.
|
||||
Signature: A6 25 54 03 74 24 C4 38 36 F4 89 70 76 1A 72 27 54 D9 E7 3D 38 CB 4C 1B 3E FD 0E DF 8A F6 7E 3D
|
||||
Privacy Mode: false
|
||||
# Protection pointer configuration
|
||||
Protection Pointer: 32
|
||||
Protection Condition: 02
|
||||
# SLIX Lock Bits
|
||||
Lock EAS: true
|
||||
Lock PPL: true
|
||||
@@ -0,0 +1,41 @@
|
||||
Filetype: Flipper NFC device
|
||||
Version: 4
|
||||
# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB
|
||||
Device type: SLIX
|
||||
# UID is common for all formats
|
||||
UID: E0 04 01 08 49 D0 DC 81
|
||||
# ISO15693-3 specific data
|
||||
# Data Storage Format Identifier
|
||||
DSFID: 01
|
||||
# Application Family Identifier
|
||||
AFI: 3D
|
||||
# IC Reference - Vendor specific meaning
|
||||
IC Reference: 01
|
||||
# Lock Bits
|
||||
Lock DSFID: true
|
||||
Lock AFI: true
|
||||
# Number of memory blocks, valid range = 1..256
|
||||
Block Count: 80
|
||||
# Size of a single memory block, valid range = 01...20 (hex)
|
||||
Block Size: 04
|
||||
Data Content: 03 0A 82 ED 86 39 61 D2 03 14 1E 32 B6 CA 00 3C 36 42 0C 33 53 30 37 32 32 34 30 30 00 00 00 00 00 FF 04 01 01 00 00 00 A3 03 1E 00 26 00 00 00 00 00 0F 00 76 03 65 01 00 00 00 00 85 01 34 00 75 09 05 00 01 00 00 00 00 00 00 00 00 00 00 00 D7 FA 00 1C 9E 1C 67 27 00 30 30 30 30 30 30 30 30 30 30 00 00 00 97 25 55 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 8C 00 30 53 48 80 DE 00 00 00 00 F4 C3 58 2B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 F3 00 2C DD C3 3E 91 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E5 FF 00 01
|
||||
# Block Security Status: 01 = locked, 00 = not locked
|
||||
Security Status: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
# SLIX specific data
|
||||
# SLIX capabilities field affects emulation modes. Possible options: Default, AcceptAllPasswords
|
||||
Capabilities: Default
|
||||
# Passwords are optional. If a password is omitted, a default value will be used
|
||||
Password Read: 00 00 00 00
|
||||
Password Write: 00 00 00 00
|
||||
Password Privacy: 0F 0F 0F 0F
|
||||
Password Destroy: 0F 0F 0F 0F
|
||||
Password EAS: 00 00 00 00
|
||||
# This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key.
|
||||
Signature: A6 25 54 03 74 24 C4 38 36 F4 89 70 76 1A 72 27 54 D9 E7 3D 38 CB 4C 1B 3E FD 0E DF 8A F6 7E 3D
|
||||
Privacy Mode: false
|
||||
# Protection pointer configuration
|
||||
Protection Pointer: 32
|
||||
Protection Condition: 02
|
||||
# SLIX Lock Bits
|
||||
Lock EAS: true
|
||||
Lock PPL: true
|
||||
@@ -0,0 +1,39 @@
|
||||
Filetype: Flipper NFC device
|
||||
Version: 4
|
||||
# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB
|
||||
Device type: SLIX
|
||||
# UID is common for all formats
|
||||
UID: E0 04 01 08 49 D0 DC 81
|
||||
# ISO15693-3 specific data
|
||||
# Data Storage Format Identifier
|
||||
DSFID: 01
|
||||
# Application Family Identifier
|
||||
AFI: 3D
|
||||
# IC Reference - Vendor specific meaning
|
||||
IC Reference: 01
|
||||
# Lock Bits
|
||||
Lock DSFID: true
|
||||
Lock AFI: true
|
||||
# Number of memory blocks, valid range = 1..256
|
||||
Block Count: 80
|
||||
# Size of a single memory block, valid range = 01...20 (hex)
|
||||
Block Size: 04
|
||||
Data Content: 03 0A 82 ED 86 39 61 D2 03 14 1E 32 B6 CA 00 3C 36 42 0C 33 53 30 37 32 32 34 30 30 00 00 00 00 00 FF 04 01 01 00 00 00 A3 03 1E 00 26 00 00 00 00 00 0F 00 76 03 65 01 00 00 00 00 85 01 34 00 75 09 05 00 01 00 00 00 00 00 00 00 00 00 00 00 D7 FA 00 1C 9E 1C 67 27 00 30 30 30 30 30 30 30 30 30 30 00 00 00 97 25 55 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 8C 00 30 53 48 80 DE 00 00 00 00 F4 C3 58 2B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 F3 00 2C DD C3 3E 91 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E5 FF 00 01
|
||||
# Block Security Status: 01 = locked, 00 = not locked
|
||||
Security Status: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
# SLIX specific data
|
||||
# Passwords are optional. If a password is omitted, a default value will be used
|
||||
Password Read: 00 00 00 00
|
||||
Password Write: 00 00 00 00
|
||||
Password Privacy: 0F 0F 0F 0F
|
||||
Password Destroy: 0F 0F 0F 0F
|
||||
Password EAS: 00 00 00 00
|
||||
# This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key.
|
||||
Signature: A6 25 54 03 74 24 C4 38 36 F4 89 70 76 1A 72 27 54 D9 E7 3D 38 CB 4C 1B 3E FD 0E DF 8A F6 7E 3D
|
||||
Privacy Mode: false
|
||||
# Protection pointer configuration
|
||||
Protection Pointer: 32
|
||||
Protection Condition: 02
|
||||
# SLIX Lock Bits
|
||||
Lock EAS: true
|
||||
Lock PPL: true
|
||||
@@ -182,6 +182,15 @@ App(
|
||||
sources=["plugins/supported_cards/itso.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="skylanders_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="skylanders_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
sources=["plugins/supported_cards/skylanders.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="nfc_start",
|
||||
targets=["f7"],
|
||||
|
||||
865
applications/main/nfc/plugins/supported_cards/skylanders.c
Normal file
865
applications/main/nfc/plugins/supported_cards/skylanders.c
Normal file
@@ -0,0 +1,865 @@
|
||||
#include "nfc_supported_card_plugin.h"
|
||||
|
||||
#include <flipper_application/flipper_application.h>
|
||||
|
||||
#include <nfc/nfc_device.h>
|
||||
#include <bit_lib/bit_lib.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
|
||||
#define TAG "Skylanders"
|
||||
|
||||
static const uint64_t skylanders_key = 0x4b0b20107ccb;
|
||||
|
||||
bool skylanders_verify(Nfc* nfc) {
|
||||
bool verified = false;
|
||||
|
||||
do {
|
||||
const uint8_t verify_sector = 0;
|
||||
uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector);
|
||||
FURI_LOG_D(TAG, "Verifying sector %u", verify_sector);
|
||||
|
||||
MfClassicKey key = {};
|
||||
bit_lib_num_to_bytes_be(skylanders_key, COUNT_OF(key.data), key.data);
|
||||
|
||||
MfClassicAuthContext auth_ctx = {};
|
||||
MfClassicError error =
|
||||
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx);
|
||||
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
|
||||
break;
|
||||
}
|
||||
|
||||
verified = true;
|
||||
} while(false);
|
||||
|
||||
return verified;
|
||||
}
|
||||
|
||||
static bool skylanders_read(Nfc* nfc, NfcDevice* device) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(device);
|
||||
|
||||
bool is_read = false;
|
||||
|
||||
MfClassicData* data = mf_classic_alloc();
|
||||
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
do {
|
||||
MfClassicType type = MfClassicType1k;
|
||||
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
data->type = type;
|
||||
MfClassicDeviceKeys keys = {};
|
||||
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||
bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_a[i].data);
|
||||
FURI_BIT_SET(keys.key_a_mask, i);
|
||||
bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_b[i].data);
|
||||
FURI_BIT_SET(keys.key_b_mask, i);
|
||||
}
|
||||
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_W(TAG, "Failed to read data");
|
||||
break;
|
||||
}
|
||||
|
||||
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
is_read = mf_classic_is_card_read(data);
|
||||
} while(false);
|
||||
|
||||
mf_classic_free(data);
|
||||
|
||||
return is_read;
|
||||
}
|
||||
|
||||
static uint8_t fill_name(const uint16_t id, FuriString* name) {
|
||||
// USED RESEARCH FROM https://github.com/silicontrip/SkyReader/blob/master/toynames.cpp#L15C1-L163C1
|
||||
// AND https://github.com/bettse/Solarbreeze/blob/master/Solarbreeze/ThePoster.swift#L438C1-L681C1
|
||||
switch(id) {
|
||||
case 0x0000:
|
||||
furi_string_cat_printf(name, "Whirlwind");
|
||||
break;
|
||||
case 0x0001:
|
||||
furi_string_cat_printf(name, "Sonic Boom");
|
||||
break;
|
||||
case 0x0002:
|
||||
furi_string_cat_printf(name, "Warnado");
|
||||
break;
|
||||
case 0x0003:
|
||||
furi_string_cat_printf(name, "Lightning Rod");
|
||||
break;
|
||||
case 0x0004:
|
||||
case 0x0194:
|
||||
furi_string_cat_printf(name, "Bash");
|
||||
break;
|
||||
case 0x0005:
|
||||
furi_string_cat_printf(name, "Terrafin");
|
||||
break;
|
||||
case 0x0006:
|
||||
furi_string_cat_printf(name, "Dino-Rang");
|
||||
break;
|
||||
case 0x0007:
|
||||
furi_string_cat_printf(name, "Prism Break");
|
||||
break;
|
||||
case 0x0008:
|
||||
furi_string_cat_printf(name, "Sunburn");
|
||||
break;
|
||||
case 0x0009:
|
||||
furi_string_cat_printf(name, "Eruptor");
|
||||
break;
|
||||
case 0x000A:
|
||||
furi_string_cat_printf(name, "Ignitor");
|
||||
break;
|
||||
case 0x000B:
|
||||
furi_string_cat_printf(name, "Flameslinger");
|
||||
break;
|
||||
case 0x000C:
|
||||
furi_string_cat_printf(name, "Zap");
|
||||
break;
|
||||
case 0x000D:
|
||||
furi_string_cat_printf(name, "Wham-Shell");
|
||||
break;
|
||||
case 0x000E:
|
||||
furi_string_cat_printf(name, "Gill Grunt");
|
||||
break;
|
||||
case 0x000F:
|
||||
furi_string_cat_printf(name, "Slam Bam");
|
||||
break;
|
||||
case 0x0010:
|
||||
case 0x01A0:
|
||||
furi_string_cat_printf(name, "Spyro");
|
||||
break;
|
||||
case 0x0011:
|
||||
furi_string_cat_printf(name, "Voodood");
|
||||
break;
|
||||
case 0x0012:
|
||||
furi_string_cat_printf(name, "Double Trouble");
|
||||
break;
|
||||
case 0x0013:
|
||||
case 0x01A3:
|
||||
furi_string_cat_printf(name, "Trigger Happy");
|
||||
break;
|
||||
case 0x0014:
|
||||
furi_string_cat_printf(name, "Drobot");
|
||||
break;
|
||||
case 0x0015:
|
||||
furi_string_cat_printf(name, "Drill Sergeant");
|
||||
break;
|
||||
case 0x0016:
|
||||
furi_string_cat_printf(name, "Boomer");
|
||||
break;
|
||||
case 0x0017:
|
||||
furi_string_cat_printf(name, "Wrecking Ball");
|
||||
break;
|
||||
case 0x0018:
|
||||
furi_string_cat_printf(name, "Camo");
|
||||
break;
|
||||
case 0x0019:
|
||||
furi_string_cat_printf(name, "Zook");
|
||||
break;
|
||||
case 0x001A:
|
||||
furi_string_cat_printf(name, "Stealth Elf");
|
||||
break;
|
||||
case 0x001B:
|
||||
furi_string_cat_printf(name, "Stump Smash");
|
||||
break;
|
||||
case 0x001C:
|
||||
furi_string_cat_printf(name, "Dark Spyro");
|
||||
break;
|
||||
case 0x001D:
|
||||
furi_string_cat_printf(name, "Hex");
|
||||
break;
|
||||
case 0x001E:
|
||||
case 0x01AE:
|
||||
furi_string_cat_printf(name, "Chop Chop");
|
||||
break;
|
||||
case 0x001F:
|
||||
furi_string_cat_printf(name, "Ghost Roaster");
|
||||
break;
|
||||
case 0x0020:
|
||||
furi_string_cat_printf(name, "Cynder");
|
||||
break;
|
||||
case 0x0064:
|
||||
furi_string_cat_printf(name, "Jet Vac");
|
||||
break;
|
||||
case 0x0065:
|
||||
furi_string_cat_printf(name, "Swarm");
|
||||
break;
|
||||
case 0x0066:
|
||||
furi_string_cat_printf(name, "Crusher");
|
||||
break;
|
||||
case 0x0067:
|
||||
furi_string_cat_printf(name, "Flashwing");
|
||||
break;
|
||||
case 0x0068:
|
||||
furi_string_cat_printf(name, "Hot Head");
|
||||
break;
|
||||
case 0x0069:
|
||||
furi_string_cat_printf(name, "Hot Dog");
|
||||
break;
|
||||
case 0x006A:
|
||||
furi_string_cat_printf(name, "Chill");
|
||||
break;
|
||||
case 0x006B:
|
||||
furi_string_cat_printf(name, "Thumpback");
|
||||
break;
|
||||
case 0x006C:
|
||||
furi_string_cat_printf(name, "Pop Fizz");
|
||||
break;
|
||||
case 0x006D:
|
||||
furi_string_cat_printf(name, "Ninjini");
|
||||
break;
|
||||
case 0x006E:
|
||||
furi_string_cat_printf(name, "Bouncer");
|
||||
break;
|
||||
case 0x006F:
|
||||
furi_string_cat_printf(name, "Sprocket");
|
||||
break;
|
||||
case 0x0070:
|
||||
furi_string_cat_printf(name, "Tree Rex");
|
||||
break;
|
||||
case 0x0071:
|
||||
furi_string_cat_printf(name, "Shroomboom");
|
||||
break;
|
||||
case 0x0072:
|
||||
furi_string_cat_printf(name, "Eye-Brawl");
|
||||
break;
|
||||
case 0x0073:
|
||||
furi_string_cat_printf(name, "Fright Rider");
|
||||
break;
|
||||
case 0x00C8:
|
||||
furi_string_cat_printf(name, "Anvil Rain");
|
||||
break;
|
||||
case 0x00C9:
|
||||
furi_string_cat_printf(name, "Treasure Chest");
|
||||
break;
|
||||
case 0x00CA:
|
||||
furi_string_cat_printf(name, "Healing Elixer");
|
||||
break;
|
||||
case 0x00CB:
|
||||
furi_string_cat_printf(name, "Ghost Swords");
|
||||
break;
|
||||
case 0x00CC:
|
||||
furi_string_cat_printf(name, "Time Twister");
|
||||
break;
|
||||
case 0x00CD:
|
||||
furi_string_cat_printf(name, "Sky-Iron Shield");
|
||||
break;
|
||||
case 0x00CE:
|
||||
furi_string_cat_printf(name, "Winged Boots");
|
||||
break;
|
||||
case 0x00CF:
|
||||
furi_string_cat_printf(name, "Sparx Dragonfly");
|
||||
break;
|
||||
case 0x00D0:
|
||||
furi_string_cat_printf(name, "Dragonfire Cannon");
|
||||
break;
|
||||
case 0x00D1:
|
||||
furi_string_cat_printf(name, "Scorpion Striker Catapult");
|
||||
break;
|
||||
case 0x00D2:
|
||||
furi_string_cat_printf(name, "Trap - Magic");
|
||||
break;
|
||||
case 0x00D3:
|
||||
furi_string_cat_printf(name, "Trap - Water");
|
||||
break;
|
||||
case 0x00D4:
|
||||
furi_string_cat_printf(name, "Trap - Air");
|
||||
break;
|
||||
case 0x00D5:
|
||||
furi_string_cat_printf(name, "Trap - Undead");
|
||||
break;
|
||||
case 0x00D6:
|
||||
furi_string_cat_printf(name, "Trap - Tech");
|
||||
break;
|
||||
case 0x00D7:
|
||||
furi_string_cat_printf(name, "Trap - Fire");
|
||||
break;
|
||||
case 0x00D8:
|
||||
furi_string_cat_printf(name, "Trap - Earth");
|
||||
break;
|
||||
case 0x00D9:
|
||||
furi_string_cat_printf(name, "Trap - Life");
|
||||
break;
|
||||
case 0x00DA:
|
||||
furi_string_cat_printf(name, "Trap - Light");
|
||||
break;
|
||||
case 0x00DB:
|
||||
furi_string_cat_printf(name, "Trap - Dark");
|
||||
break;
|
||||
case 0x00DC:
|
||||
furi_string_cat_printf(name, "Trap - Kaos");
|
||||
break;
|
||||
case 0x00E6:
|
||||
furi_string_cat_printf(name, "Hand Of Fate");
|
||||
break;
|
||||
case 0x00E7:
|
||||
furi_string_cat_printf(name, "Piggy Bank");
|
||||
break;
|
||||
case 0x00E8:
|
||||
furi_string_cat_printf(name, "Rocket Ram");
|
||||
break;
|
||||
case 0x00E9:
|
||||
furi_string_cat_printf(name, "Tiki Speaky");
|
||||
break;
|
||||
case 0x00EB:
|
||||
furi_string_cat_printf(name, "Imaginite Mystery Chest");
|
||||
break;
|
||||
case 0x012C:
|
||||
furi_string_cat_printf(name, "Dragons Peak");
|
||||
break;
|
||||
case 0x012D:
|
||||
furi_string_cat_printf(name, "Empire of Ice");
|
||||
break;
|
||||
case 0x012E:
|
||||
furi_string_cat_printf(name, "Pirate Seas");
|
||||
break;
|
||||
case 0x012F:
|
||||
furi_string_cat_printf(name, "Darklight Crypt");
|
||||
break;
|
||||
case 0x0130:
|
||||
furi_string_cat_printf(name, "Volcanic Vault");
|
||||
break;
|
||||
case 0x0131:
|
||||
furi_string_cat_printf(name, "Mirror Of Mystery");
|
||||
break;
|
||||
case 0x0132:
|
||||
furi_string_cat_printf(name, "Nightmare Express");
|
||||
break;
|
||||
case 0x0133:
|
||||
furi_string_cat_printf(name, "Sunscraper Spire");
|
||||
break;
|
||||
case 0x0134:
|
||||
furi_string_cat_printf(name, "Midnight Museum");
|
||||
break;
|
||||
case 0x01C2:
|
||||
furi_string_cat_printf(name, "Gusto");
|
||||
break;
|
||||
case 0x01C3:
|
||||
furi_string_cat_printf(name, "Thunderbolt");
|
||||
break;
|
||||
case 0x01C4:
|
||||
furi_string_cat_printf(name, "Fling Kong");
|
||||
break;
|
||||
case 0x01C5:
|
||||
furi_string_cat_printf(name, "Blades");
|
||||
break;
|
||||
case 0x01C6:
|
||||
furi_string_cat_printf(name, "Wallop");
|
||||
break;
|
||||
case 0x01C7:
|
||||
furi_string_cat_printf(name, "Head Rush");
|
||||
break;
|
||||
case 0x01C8:
|
||||
furi_string_cat_printf(name, "Fist Bump");
|
||||
break;
|
||||
case 0x01C9:
|
||||
furi_string_cat_printf(name, "Rocky Roll");
|
||||
break;
|
||||
case 0x01CA:
|
||||
furi_string_cat_printf(name, "Wildfire");
|
||||
break;
|
||||
case 0x01CB:
|
||||
furi_string_cat_printf(name, "Ka Boom");
|
||||
break;
|
||||
case 0x01CC:
|
||||
furi_string_cat_printf(name, "Trail Blazer");
|
||||
break;
|
||||
case 0x01CD:
|
||||
furi_string_cat_printf(name, "Torch");
|
||||
break;
|
||||
case 0x01CE:
|
||||
furi_string_cat_printf(name, "Snap Shot");
|
||||
break;
|
||||
case 0x01CF:
|
||||
furi_string_cat_printf(name, "Lob Star");
|
||||
break;
|
||||
case 0x01D0:
|
||||
furi_string_cat_printf(name, "Flip Wreck");
|
||||
break;
|
||||
case 0x01D1:
|
||||
furi_string_cat_printf(name, "Echo");
|
||||
break;
|
||||
case 0x01D2:
|
||||
furi_string_cat_printf(name, "Blastermind");
|
||||
break;
|
||||
case 0x01D3:
|
||||
furi_string_cat_printf(name, "Enigma");
|
||||
break;
|
||||
case 0x01D4:
|
||||
furi_string_cat_printf(name, "Deja Vu");
|
||||
break;
|
||||
case 0x01D5:
|
||||
furi_string_cat_printf(name, "Cobra Cadabra");
|
||||
break;
|
||||
case 0x01D6:
|
||||
furi_string_cat_printf(name, "Jawbreaker");
|
||||
break;
|
||||
case 0x01D7:
|
||||
furi_string_cat_printf(name, "Gearshift");
|
||||
break;
|
||||
case 0x01D8:
|
||||
furi_string_cat_printf(name, "Chopper");
|
||||
break;
|
||||
case 0x01D9:
|
||||
furi_string_cat_printf(name, "Tread Head");
|
||||
break;
|
||||
case 0x01DA:
|
||||
furi_string_cat_printf(name, "Bushwhack");
|
||||
break;
|
||||
case 0x01DB:
|
||||
furi_string_cat_printf(name, "Tuff Luck");
|
||||
break;
|
||||
case 0x01DC:
|
||||
furi_string_cat_printf(name, "Food Fight");
|
||||
break;
|
||||
case 0x01DD:
|
||||
furi_string_cat_printf(name, "High Five");
|
||||
break;
|
||||
case 0x01DE:
|
||||
furi_string_cat_printf(name, "Krypt King");
|
||||
break;
|
||||
case 0x01DF:
|
||||
furi_string_cat_printf(name, "Short Cut");
|
||||
break;
|
||||
case 0x01E0:
|
||||
furi_string_cat_printf(name, "Bat Spin");
|
||||
break;
|
||||
case 0x01E1:
|
||||
furi_string_cat_printf(name, "Funny Bone");
|
||||
break;
|
||||
case 0x01E2:
|
||||
furi_string_cat_printf(name, "Knight light");
|
||||
break;
|
||||
case 0x01E3:
|
||||
furi_string_cat_printf(name, "Spotlight");
|
||||
break;
|
||||
case 0x01E4:
|
||||
furi_string_cat_printf(name, "Knight Mare");
|
||||
break;
|
||||
case 0x01E5:
|
||||
furi_string_cat_printf(name, "Blackout");
|
||||
break;
|
||||
case 0x01F6:
|
||||
furi_string_cat_printf(name, "Bop");
|
||||
break;
|
||||
case 0x01F7:
|
||||
furi_string_cat_printf(name, "Spry");
|
||||
break;
|
||||
case 0x01F8:
|
||||
furi_string_cat_printf(name, "Hijinx");
|
||||
break;
|
||||
case 0x01F9:
|
||||
furi_string_cat_printf(name, "Terrabite");
|
||||
break;
|
||||
case 0x01FA:
|
||||
furi_string_cat_printf(name, "Breeze");
|
||||
break;
|
||||
case 0x01FB:
|
||||
furi_string_cat_printf(name, "Weeruptor");
|
||||
break;
|
||||
case 0x01FC:
|
||||
furi_string_cat_printf(name, "Pet Vac");
|
||||
break;
|
||||
case 0x01FD:
|
||||
furi_string_cat_printf(name, "Small Fry");
|
||||
break;
|
||||
case 0x01FE:
|
||||
furi_string_cat_printf(name, "Drobit");
|
||||
break;
|
||||
case 0x0202:
|
||||
furi_string_cat_printf(name, "Gill Runt");
|
||||
break;
|
||||
case 0x0207:
|
||||
furi_string_cat_printf(name, "Trigger Snappy");
|
||||
break;
|
||||
case 0x020E:
|
||||
furi_string_cat_printf(name, "Whisper Elf");
|
||||
break;
|
||||
case 0x021C:
|
||||
furi_string_cat_printf(name, "Barkley");
|
||||
break;
|
||||
case 0x021D:
|
||||
furi_string_cat_printf(name, "Thumpling");
|
||||
break;
|
||||
case 0x021E:
|
||||
furi_string_cat_printf(name, "Mini Jini");
|
||||
break;
|
||||
case 0x021F:
|
||||
furi_string_cat_printf(name, "Eye Small");
|
||||
break;
|
||||
case 0x0259:
|
||||
furi_string_cat_printf(name, "King Pen");
|
||||
break;
|
||||
case 0x0265:
|
||||
furi_string_cat_printf(name, "Golden Queen");
|
||||
break;
|
||||
case 0x02AD:
|
||||
furi_string_cat_printf(name, "Fire Acorn");
|
||||
break;
|
||||
case 0x03E8:
|
||||
furi_string_cat_printf(name, "(Boom) Jet");
|
||||
break;
|
||||
case 0x03E9:
|
||||
furi_string_cat_printf(name, "(Free) Ranger");
|
||||
break;
|
||||
case 0x03EA:
|
||||
furi_string_cat_printf(name, "(Rubble) Rouser");
|
||||
break;
|
||||
case 0x03EB:
|
||||
furi_string_cat_printf(name, "(Doom) Stone");
|
||||
break;
|
||||
case 0x03EC:
|
||||
furi_string_cat_printf(name, "Blast Zone");
|
||||
break;
|
||||
case 0x03ED:
|
||||
furi_string_cat_printf(name, "(Fire) Kraken");
|
||||
break;
|
||||
case 0x03EE:
|
||||
furi_string_cat_printf(name, "(Stink) Bomb");
|
||||
break;
|
||||
case 0x03EF:
|
||||
furi_string_cat_printf(name, "(Grilla) Drilla");
|
||||
break;
|
||||
case 0x03F0:
|
||||
furi_string_cat_printf(name, "(Hoot) Loop");
|
||||
break;
|
||||
case 0x03F1:
|
||||
furi_string_cat_printf(name, "(Trap) Shadow");
|
||||
break;
|
||||
case 0x03F2:
|
||||
furi_string_cat_printf(name, "(Magna) Charge");
|
||||
break;
|
||||
case 0x03F3:
|
||||
furi_string_cat_printf(name, "(Spy) Rise");
|
||||
break;
|
||||
case 0x03F4:
|
||||
furi_string_cat_printf(name, "(Night) Shift");
|
||||
break;
|
||||
case 0x03F5:
|
||||
furi_string_cat_printf(name, "(Rattle) Shake");
|
||||
break;
|
||||
case 0x03F6:
|
||||
furi_string_cat_printf(name, "(Freeze) Blade");
|
||||
break;
|
||||
case 0x03F7:
|
||||
furi_string_cat_printf(name, "Wash Buckler");
|
||||
break;
|
||||
case 0x07D0:
|
||||
furi_string_cat_printf(name, "Boom (Jet)");
|
||||
break;
|
||||
case 0x07D1:
|
||||
furi_string_cat_printf(name, "Free (Ranger)");
|
||||
break;
|
||||
case 0x07D2:
|
||||
furi_string_cat_printf(name, "Rubble (Rouser)");
|
||||
break;
|
||||
case 0x07D3:
|
||||
furi_string_cat_printf(name, "Doom (Stone)");
|
||||
break;
|
||||
case 0x07D4:
|
||||
furi_string_cat_printf(name, "Blast Zone (Head)");
|
||||
break;
|
||||
case 0x07D5:
|
||||
furi_string_cat_printf(name, "Fire (Kraken)");
|
||||
break;
|
||||
case 0x07D6:
|
||||
furi_string_cat_printf(name, "Stink (Bomb)");
|
||||
break;
|
||||
case 0x07D7:
|
||||
furi_string_cat_printf(name, "Grilla (Drilla)");
|
||||
break;
|
||||
case 0x07D8:
|
||||
furi_string_cat_printf(name, "Hoot (Loop)");
|
||||
break;
|
||||
case 0x07D9:
|
||||
furi_string_cat_printf(name, "Trap (Shadow)");
|
||||
break;
|
||||
case 0x07DA:
|
||||
furi_string_cat_printf(name, "Magna (Charge)");
|
||||
break;
|
||||
case 0x07DB:
|
||||
furi_string_cat_printf(name, "Spy (Rise)");
|
||||
break;
|
||||
case 0x07DC:
|
||||
furi_string_cat_printf(name, "Night (Shift)");
|
||||
break;
|
||||
case 0x07DD:
|
||||
furi_string_cat_printf(name, "Rattle (Shake)");
|
||||
break;
|
||||
case 0x07DE:
|
||||
furi_string_cat_printf(name, "Freeze (Blade)");
|
||||
break;
|
||||
case 0x07DF:
|
||||
furi_string_cat_printf(name, "Wash Buckler (Head)");
|
||||
break;
|
||||
case 0x0BB8:
|
||||
furi_string_cat_printf(name, "Scratch");
|
||||
break;
|
||||
case 0x0BB9:
|
||||
furi_string_cat_printf(name, "Pop Thorn");
|
||||
break;
|
||||
case 0x0BBA:
|
||||
furi_string_cat_printf(name, "Slobber Tooth");
|
||||
break;
|
||||
case 0x0BBB:
|
||||
furi_string_cat_printf(name, "Scorp");
|
||||
break;
|
||||
case 0x0BBC:
|
||||
furi_string_cat_printf(name, "Fryno");
|
||||
break;
|
||||
case 0x0BBD:
|
||||
furi_string_cat_printf(name, "Smolderdash");
|
||||
break;
|
||||
case 0x0BBE:
|
||||
furi_string_cat_printf(name, "Bumble Blast");
|
||||
break;
|
||||
case 0x0BBF:
|
||||
furi_string_cat_printf(name, "Zoo Lou");
|
||||
break;
|
||||
case 0x0BC0:
|
||||
furi_string_cat_printf(name, "Dune Bug");
|
||||
break;
|
||||
case 0x0BC1:
|
||||
furi_string_cat_printf(name, "Star Strike");
|
||||
break;
|
||||
case 0x0BC2:
|
||||
furi_string_cat_printf(name, "Countdown");
|
||||
break;
|
||||
case 0x0BC3:
|
||||
furi_string_cat_printf(name, "Wind Up");
|
||||
break;
|
||||
case 0x0BC4:
|
||||
furi_string_cat_printf(name, "Roller Brawl");
|
||||
break;
|
||||
case 0x0BC5:
|
||||
furi_string_cat_printf(name, "Grim Creeper");
|
||||
break;
|
||||
case 0x0BC6:
|
||||
furi_string_cat_printf(name, "Rip Tide");
|
||||
break;
|
||||
case 0x0BC7:
|
||||
furi_string_cat_printf(name, "Punk Shock");
|
||||
break;
|
||||
case 0x0C80:
|
||||
furi_string_cat_printf(name, "Battle Hammer");
|
||||
break;
|
||||
case 0x0C81:
|
||||
furi_string_cat_printf(name, "Sky Diamond");
|
||||
break;
|
||||
case 0x0C82:
|
||||
furi_string_cat_printf(name, "Platinum Sheep");
|
||||
break;
|
||||
case 0x0C83:
|
||||
furi_string_cat_printf(name, "Groove Machine");
|
||||
break;
|
||||
case 0x0C84:
|
||||
furi_string_cat_printf(name, "UFO Hat");
|
||||
break;
|
||||
case 0x0C94:
|
||||
furi_string_cat_printf(name, "Jet Stream");
|
||||
break;
|
||||
case 0x0C95:
|
||||
furi_string_cat_printf(name, "Tomb Buggy");
|
||||
break;
|
||||
case 0x0C96:
|
||||
furi_string_cat_printf(name, "Reef Ripper");
|
||||
break;
|
||||
case 0x0C97:
|
||||
furi_string_cat_printf(name, "Burn Cycle");
|
||||
break;
|
||||
case 0x0C98:
|
||||
furi_string_cat_printf(name, "Hot Streak");
|
||||
break;
|
||||
case 0x0C99:
|
||||
furi_string_cat_printf(name, "Shark Tank");
|
||||
break;
|
||||
case 0x0C9A:
|
||||
furi_string_cat_printf(name, "Thump Truck");
|
||||
break;
|
||||
case 0x0C9B:
|
||||
furi_string_cat_printf(name, "Crypt Crusher");
|
||||
break;
|
||||
case 0x0C9C:
|
||||
furi_string_cat_printf(name, "Stealth Stinger");
|
||||
break;
|
||||
case 0x0C9F:
|
||||
furi_string_cat_printf(name, "Dive Bomber");
|
||||
break;
|
||||
case 0x0CA0:
|
||||
furi_string_cat_printf(name, "Sky Slicer");
|
||||
break;
|
||||
case 0x0CA1:
|
||||
furi_string_cat_printf(name, "Clown Cruiser");
|
||||
break;
|
||||
case 0x0CA2:
|
||||
furi_string_cat_printf(name, "Gold Rusher");
|
||||
break;
|
||||
case 0x0CA3:
|
||||
furi_string_cat_printf(name, "Shield Striker");
|
||||
break;
|
||||
case 0x0CA4:
|
||||
furi_string_cat_printf(name, "Sun Runner");
|
||||
break;
|
||||
case 0x0CA5:
|
||||
furi_string_cat_printf(name, "Sea Shadow");
|
||||
break;
|
||||
case 0x0CA6:
|
||||
furi_string_cat_printf(name, "Splatter Splasher");
|
||||
break;
|
||||
case 0x0CA7:
|
||||
furi_string_cat_printf(name, "Soda Skimmer");
|
||||
break;
|
||||
case 0x0CA8:
|
||||
furi_string_cat_printf(name, "Barrel Blaster");
|
||||
break;
|
||||
case 0x0CA9:
|
||||
furi_string_cat_printf(name, "Buzz Wing");
|
||||
break;
|
||||
case 0x0CE4:
|
||||
furi_string_cat_printf(name, "Sheep Wreck Island");
|
||||
break;
|
||||
case 0x0CE5:
|
||||
furi_string_cat_printf(name, "Tower of Time");
|
||||
break;
|
||||
case 0x0CE6:
|
||||
furi_string_cat_printf(name, "Fiery Forge");
|
||||
break;
|
||||
case 0x0CE7:
|
||||
furi_string_cat_printf(name, "Arkeyan Crossbow");
|
||||
break;
|
||||
case 0x0D48:
|
||||
furi_string_cat_printf(name, "Fiesta");
|
||||
break;
|
||||
case 0x0D49:
|
||||
furi_string_cat_printf(name, "High Volt");
|
||||
break;
|
||||
case 0x0D4A:
|
||||
furi_string_cat_printf(name, "Splat");
|
||||
break;
|
||||
case 0x0D4E:
|
||||
furi_string_cat_printf(name, "Stormblade");
|
||||
break;
|
||||
case 0x0D53:
|
||||
furi_string_cat_printf(name, "Smash It");
|
||||
break;
|
||||
case 0x0D54:
|
||||
furi_string_cat_printf(name, "Spitfire");
|
||||
break;
|
||||
case 0x0D55:
|
||||
furi_string_cat_printf(name, "Hurricane Jet-Vac");
|
||||
break;
|
||||
case 0x0D56:
|
||||
furi_string_cat_printf(name, "Double Dare Trigger Happy");
|
||||
break;
|
||||
case 0x0D57:
|
||||
furi_string_cat_printf(name, "Super Shot Stealth Elf");
|
||||
break;
|
||||
case 0x0D58:
|
||||
furi_string_cat_printf(name, "Shark Shooter Terrafin");
|
||||
break;
|
||||
case 0x0D59:
|
||||
furi_string_cat_printf(name, "Bone Bash Roller Brawl");
|
||||
break;
|
||||
case 0x0D5C:
|
||||
furi_string_cat_printf(name, "Big Bubble Pop Fizz");
|
||||
break;
|
||||
case 0x0D5D:
|
||||
furi_string_cat_printf(name, "Lava Lance Eruptor");
|
||||
break;
|
||||
case 0x0D5E:
|
||||
furi_string_cat_printf(name, "Deep Dive Gill Grunt");
|
||||
break;
|
||||
case 0x0D5F:
|
||||
furi_string_cat_printf(name, "Turbo Charge Donkey Kong");
|
||||
break;
|
||||
case 0x0D60:
|
||||
furi_string_cat_printf(name, "Hammer Slam Bowser");
|
||||
break;
|
||||
case 0x0D61:
|
||||
furi_string_cat_printf(name, "Dive-Clops");
|
||||
break;
|
||||
case 0x0D62:
|
||||
furi_string_cat_printf(name, "Astroblast");
|
||||
break;
|
||||
case 0x0D63:
|
||||
furi_string_cat_printf(name, "Nightfall");
|
||||
break;
|
||||
case 0x0D64:
|
||||
furi_string_cat_printf(name, "Thrillipede");
|
||||
break;
|
||||
case 0x0DAC:
|
||||
furi_string_cat_printf(name, "Sky Trophy");
|
||||
break;
|
||||
case 0x0DAD:
|
||||
furi_string_cat_printf(name, "Land Trophy");
|
||||
break;
|
||||
case 0x0DAE:
|
||||
furi_string_cat_printf(name, "Sea Trophy");
|
||||
break;
|
||||
case 0x0DAF:
|
||||
furi_string_cat_printf(name, "Kaos Trophy");
|
||||
break;
|
||||
default:
|
||||
furi_string_cat_printf(name, "Unknown");
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool skylanders_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_assert(device);
|
||||
|
||||
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||
|
||||
bool parsed = false;
|
||||
FuriString* name = furi_string_alloc();
|
||||
|
||||
do {
|
||||
// verify key
|
||||
const uint8_t verify_sector = 0;
|
||||
MfClassicSectorTrailer* sec_tr =
|
||||
mf_classic_get_sector_trailer_by_sector(data, verify_sector);
|
||||
uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6);
|
||||
if(key != skylanders_key) break;
|
||||
|
||||
const uint16_t id = (uint16_t)*data->block[1].data;
|
||||
if(id == 0) break;
|
||||
|
||||
bool success = fill_name(id, name);
|
||||
if(!success) break;
|
||||
|
||||
furi_string_printf(parsed_data, "\e#Skylanders\n%s", furi_string_get_cstr(name));
|
||||
|
||||
parsed = true;
|
||||
|
||||
} while(false);
|
||||
|
||||
furi_string_free(name);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/* Actual implementation of app<>plugin interface */
|
||||
static const NfcSupportedCardsPlugin skylanders_plugin = {
|
||||
.protocol = NfcProtocolMfClassic,
|
||||
.verify = skylanders_verify,
|
||||
.read = skylanders_read,
|
||||
.parse = skylanders_parse,
|
||||
};
|
||||
|
||||
/* Plugin descriptor to comply with basic plugin specification */
|
||||
static const FlipperAppPluginDescriptor skylanders_plugin_descriptor = {
|
||||
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||
.entry_point = &skylanders_plugin,
|
||||
};
|
||||
|
||||
/* Plugin entry point - must return a pointer to const descriptor */
|
||||
const FlipperAppPluginDescriptor* skylanders_plugin_ep(void) {
|
||||
return &skylanders_plugin_descriptor;
|
||||
}
|
||||
@@ -16,6 +16,7 @@ App(
|
||||
"elements.h",
|
||||
"view_dispatcher.h",
|
||||
"view_stack.h",
|
||||
"view_holder.h",
|
||||
"modules/button_menu.h",
|
||||
"modules/byte_input.h",
|
||||
"modules/popup.h",
|
||||
|
||||
@@ -121,7 +121,8 @@ void elements_multiline_text_aligned(
|
||||
/** Draw multiline text
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x, y top left corner coordinates
|
||||
* @param x top left corner coordinates
|
||||
* @param y top left corner coordinates
|
||||
* @param text string (possible multiline)
|
||||
*/
|
||||
void elements_multiline_text(Canvas* canvas, int32_t x, int32_t y, const char* text);
|
||||
@@ -129,7 +130,8 @@ void elements_multiline_text(Canvas* canvas, int32_t x, int32_t y, const char* t
|
||||
/** Draw framed multiline text
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x, y top left corner coordinates
|
||||
* @param x top left corner coordinates
|
||||
* @param y top left corner coordinates
|
||||
* @param text string (possible multiline)
|
||||
*/
|
||||
void elements_multiline_text_framed(Canvas* canvas, int32_t x, int32_t y, const char* text);
|
||||
@@ -137,8 +139,10 @@ void elements_multiline_text_framed(Canvas* canvas, int32_t x, int32_t y, const
|
||||
/** Draw slightly rounded frame
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x, y top left corner coordinates
|
||||
* @param width, height size of frame
|
||||
* @param x top left corner coordinates
|
||||
* @param y top left corner coordinates
|
||||
* @param width width of frame
|
||||
* @param height height of frame
|
||||
*/
|
||||
void elements_slightly_rounded_frame(
|
||||
Canvas* canvas,
|
||||
@@ -150,8 +154,10 @@ void elements_slightly_rounded_frame(
|
||||
/** Draw slightly rounded box
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x, y top left corner coordinates
|
||||
* @param width, height size of box
|
||||
* @param x top left corner coordinates
|
||||
* @param y top left corner coordinates
|
||||
* @param width height of box
|
||||
* @param height height of box
|
||||
*/
|
||||
void elements_slightly_rounded_box(
|
||||
Canvas* canvas,
|
||||
@@ -163,8 +169,10 @@ void elements_slightly_rounded_box(
|
||||
/** Draw bold rounded frame
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x, y top left corner coordinates
|
||||
* @param width, height size of frame
|
||||
* @param x top left corner coordinates
|
||||
* @param y top left corner coordinates
|
||||
* @param width width of frame
|
||||
* @param height height of frame
|
||||
*/
|
||||
void elements_bold_rounded_frame(Canvas* canvas, int32_t x, int32_t y, size_t width, size_t height);
|
||||
|
||||
|
||||
@@ -215,6 +215,26 @@ void submenu_add_item(
|
||||
true);
|
||||
}
|
||||
|
||||
void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label) {
|
||||
furi_check(submenu);
|
||||
furi_check(label);
|
||||
|
||||
with_view_model(
|
||||
submenu->view,
|
||||
SubmenuModel * model,
|
||||
{
|
||||
SubmenuItemArray_it_t it;
|
||||
for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it);
|
||||
SubmenuItemArray_next(it)) {
|
||||
if(index == SubmenuItemArray_cref(it)->index) {
|
||||
furi_string_set_str(SubmenuItemArray_cref(it)->label, label);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void submenu_reset(Submenu* submenu) {
|
||||
furi_check(submenu);
|
||||
|
||||
@@ -230,6 +250,25 @@ void submenu_reset(Submenu* submenu) {
|
||||
true);
|
||||
}
|
||||
|
||||
uint32_t submenu_get_selected_item(Submenu* submenu) {
|
||||
furi_check(submenu);
|
||||
|
||||
uint32_t selected_item_index = 0;
|
||||
|
||||
with_view_model(
|
||||
submenu->view,
|
||||
SubmenuModel * model,
|
||||
{
|
||||
if(model->position < SubmenuItemArray_size(model->items)) {
|
||||
const SubmenuItem* item = SubmenuItemArray_cget(model->items, model->position);
|
||||
selected_item_index = item->index;
|
||||
}
|
||||
},
|
||||
false);
|
||||
|
||||
return selected_item_index;
|
||||
}
|
||||
|
||||
void submenu_set_selected_item(Submenu* submenu, uint32_t index) {
|
||||
furi_check(submenu);
|
||||
with_view_model(
|
||||
|
||||
@@ -53,16 +53,32 @@ void submenu_add_item(
|
||||
SubmenuItemCallback callback,
|
||||
void* callback_context);
|
||||
|
||||
/** Change label of an existing item
|
||||
*
|
||||
* @param submenu Submenu instance
|
||||
* @param index The index of the item
|
||||
* @param label The new label
|
||||
*/
|
||||
void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label);
|
||||
|
||||
/** Remove all items from submenu
|
||||
*
|
||||
* @param submenu Submenu instance
|
||||
*/
|
||||
void submenu_reset(Submenu* submenu);
|
||||
|
||||
/** Set submenu item selector
|
||||
/** Get submenu selected item index
|
||||
*
|
||||
* @param submenu Submenu instance
|
||||
* @param index The index
|
||||
*
|
||||
* @return Index of the selected item
|
||||
*/
|
||||
uint32_t submenu_get_selected_item(Submenu* submenu);
|
||||
|
||||
/** Set submenu selected item by index
|
||||
*
|
||||
* @param submenu Submenu instance
|
||||
* @param index The index of the selected item
|
||||
*/
|
||||
void submenu_set_selected_item(Submenu* submenu, uint32_t index);
|
||||
|
||||
|
||||
@@ -97,10 +97,11 @@ void view_free_model(View* view) {
|
||||
furi_mutex_free(model->mutex);
|
||||
free(model->data);
|
||||
free(model);
|
||||
view->model = NULL;
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
view->model = NULL;
|
||||
view->model_type = ViewModelTypeNone;
|
||||
}
|
||||
|
||||
void* view_get_model(View* view) {
|
||||
|
||||
@@ -41,7 +41,7 @@ static DialogMessageButton about_screen_product(DialogsApp* dialogs, DialogMessa
|
||||
static DialogMessageButton about_screen_address(DialogsApp* dialogs, DialogMessage* message) {
|
||||
DialogMessageButton result;
|
||||
|
||||
const char* screen_text = "Flipper Devices Inc\n"
|
||||
const char* screen_text = "Flipper Devices Inc.\n"
|
||||
"Suite B #551, 2803\n"
|
||||
"Philadelphia Pike, Claymont\n"
|
||||
"DE, USA 19703\n";
|
||||
@@ -56,7 +56,7 @@ static DialogMessageButton about_screen_compliance(DialogsApp* dialogs, DialogMe
|
||||
DialogMessageButton result;
|
||||
|
||||
const char* screen_text = "For all compliance\n"
|
||||
"certificates please visit:\n"
|
||||
"certificates, please visit:\n"
|
||||
"www.flipp.dev/compliance";
|
||||
|
||||
dialog_message_set_text(message, screen_text, 0, 0, AlignLeft, AlignTop);
|
||||
@@ -97,7 +97,7 @@ static DialogMessageButton about_screen_cert_china_0(DialogsApp* dialogs, Dialog
|
||||
static DialogMessageButton about_screen_cert_china_1(DialogsApp* dialogs, DialogMessage* message) {
|
||||
DialogMessageButton result;
|
||||
|
||||
dialog_message_set_icon(message, &I_CertificationChina1_122x47, 3, 3);
|
||||
dialog_message_set_icon(message, &I_CertificationChina1_124x47, 3, 3);
|
||||
dialog_message_set_text(
|
||||
message, furi_hal_version_get_srrc_id(), 55, 11, AlignLeft, AlignBottom);
|
||||
result = dialog_message_show(dialogs, message);
|
||||
@@ -227,9 +227,11 @@ int32_t about_settings_app(void* p) {
|
||||
|
||||
while(1) {
|
||||
if(screen_index >= COUNT_OF(about_screens) - 1) {
|
||||
dialog_message_set_buttons(message, "Back", NULL, NULL);
|
||||
dialog_message_set_buttons(message, "Prev.", NULL, NULL);
|
||||
} else if(screen_index == 0) {
|
||||
dialog_message_set_buttons(message, NULL, NULL, "Next");
|
||||
} else {
|
||||
dialog_message_set_buttons(message, "Back", NULL, "Next");
|
||||
dialog_message_set_buttons(message, "Prev.", NULL, "Next");
|
||||
}
|
||||
|
||||
screen_result = about_screens[screen_index](dialogs, message);
|
||||
|
||||
@@ -10,10 +10,10 @@ void bt_settings_scene_forget_dev_confirm_dialog_callback(DialogExResult result,
|
||||
void bt_settings_scene_forget_dev_confirm_on_enter(void* context) {
|
||||
BtSettingsApp* app = context;
|
||||
DialogEx* dialog = app->dialog;
|
||||
dialog_ex_set_header(dialog, "Unpair All Devices?", 64, 3, AlignCenter, AlignTop);
|
||||
dialog_ex_set_header(dialog, "Unpair All Devices?", 64, 0, AlignCenter, AlignTop);
|
||||
dialog_ex_set_text(
|
||||
dialog, "All previous pairings\nwill be lost!", 64, 22, AlignCenter, AlignTop);
|
||||
dialog_ex_set_left_button_text(dialog, "Back");
|
||||
dialog, "All previous pairings\nwill be lost!", 64, 14, AlignCenter, AlignTop);
|
||||
dialog_ex_set_left_button_text(dialog, "Cancel");
|
||||
dialog_ex_set_right_button_text(dialog, "Unpair");
|
||||
dialog_ex_set_context(dialog, app);
|
||||
dialog_ex_set_result_callback(dialog, bt_settings_scene_forget_dev_confirm_dialog_callback);
|
||||
|
||||
@@ -53,7 +53,7 @@ void bt_settings_scene_start_on_enter(void* context) {
|
||||
variable_item_set_current_value_index(item, BtSettingOff);
|
||||
variable_item_set_current_value_text(item, bt_settings_text[BtSettingOff]);
|
||||
}
|
||||
variable_item_list_add(var_item_list, "Forget All Paired Devices", 1, NULL, NULL);
|
||||
variable_item_list_add(var_item_list, "Unpair All Devices", 1, NULL, NULL);
|
||||
variable_item_list_set_enter_callback(
|
||||
var_item_list, bt_settings_scene_start_var_list_enter_callback, app);
|
||||
} else {
|
||||
|
||||
@@ -92,6 +92,7 @@ void desktop_settings_app_free(DesktopSettingsApp* app) {
|
||||
extern int32_t desktop_settings_app(void* p) {
|
||||
DesktopSettingsApp* app = desktop_settings_app_alloc();
|
||||
DESKTOP_SETTINGS_LOAD(&app->settings);
|
||||
|
||||
if(p && (strcmp(p, DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG) == 0)) {
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupHowto);
|
||||
} else {
|
||||
@@ -99,6 +100,9 @@ extern int32_t desktop_settings_app(void* p) {
|
||||
}
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
DESKTOP_SETTINGS_SAVE(&app->settings);
|
||||
desktop_settings_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -40,5 +40,7 @@ typedef struct {
|
||||
PinCode pincode_buffer;
|
||||
bool pincode_buffer_filled;
|
||||
|
||||
uint8_t menu_idx;
|
||||
uint32_t pin_menu_idx;
|
||||
uint32_t quick_apps_menu_idx;
|
||||
uint32_t quick_apps_direction_menu_idx;
|
||||
} DesktopSettingsApp;
|
||||
|
||||
@@ -9,3 +9,6 @@ ADD_SCENE(desktop_settings, pin_setup, PinSetup)
|
||||
ADD_SCENE(desktop_settings, pin_setup_howto, PinSetupHowto)
|
||||
ADD_SCENE(desktop_settings, pin_setup_howto2, PinSetupHowto2)
|
||||
ADD_SCENE(desktop_settings, pin_setup_done, PinSetupDone)
|
||||
|
||||
ADD_SCENE(desktop_settings, quick_apps_menu, QuickAppsMenu)
|
||||
ADD_SCENE(desktop_settings, quick_apps_direction_menu, QuickAppsDirectionMenu)
|
||||
@@ -9,11 +9,17 @@
|
||||
#define APPS_COUNT (FLIPPER_APPS_COUNT + FLIPPER_EXTERNAL_APPS_COUNT)
|
||||
|
||||
#define DEFAULT_INDEX (0)
|
||||
#define EXTERNAL_BROWSER_NAME ("Apps Menu (Default)")
|
||||
#define PASSPORT_NAME ("Passport (Default)")
|
||||
#define EXTERNAL_BROWSER_NAME ("( ) Apps Menu (Default)")
|
||||
#define EXTERNAL_BROWSER_NAME_SELECTED ("(*) Apps Menu (Default)")
|
||||
#define PASSPORT_NAME ("( ) Passport (Default)")
|
||||
#define PASSPORT_NAME_SELECTED ("(*) Passport (Default)")
|
||||
|
||||
#define SELECTED_PREFIX ("(*) ")
|
||||
#define NOT_SELECTED_PREFIX ("( ) ")
|
||||
|
||||
#define EXTERNAL_APPLICATION_INDEX (1)
|
||||
#define EXTERNAL_APPLICATION_NAME ("[Select App]")
|
||||
#define EXTERNAL_APPLICATION_NAME ("( ) [Select App]")
|
||||
#define EXTERNAL_APPLICATION_NAME_SELECTED ("(*) [Select App]")
|
||||
|
||||
#define PRESELECTED_SPECIAL 0xffffffff
|
||||
|
||||
@@ -61,7 +67,6 @@ void desktop_settings_scene_favorite_on_enter(void* context) {
|
||||
scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite);
|
||||
uint32_t pre_select_item = PRESELECTED_SPECIAL;
|
||||
FavoriteApp* curr_favorite_app = NULL;
|
||||
bool is_dummy_app = false;
|
||||
bool default_passport = false;
|
||||
|
||||
if((favorite_id & SCENE_STATE_SET_DUMMY_APP) == 0) {
|
||||
@@ -74,7 +79,6 @@ void desktop_settings_scene_favorite_on_enter(void* context) {
|
||||
favorite_id &= ~(SCENE_STATE_SET_DUMMY_APP);
|
||||
furi_assert(favorite_id < DummyAppNumber);
|
||||
curr_favorite_app = &app->settings.dummy_apps[favorite_id];
|
||||
is_dummy_app = true;
|
||||
default_passport = true;
|
||||
}
|
||||
|
||||
@@ -94,29 +98,76 @@ void desktop_settings_scene_favorite_on_enter(void* context) {
|
||||
desktop_settings_scene_favorite_submenu_callback,
|
||||
app);
|
||||
|
||||
if(!is_dummy_app) {
|
||||
for(size_t i = 0; i < APPS_COUNT; i++) {
|
||||
const char* name = favorite_fap_get_app_name(i);
|
||||
FuriString* full_name = furi_string_alloc();
|
||||
|
||||
submenu_add_item(
|
||||
submenu, name, i + 2, desktop_settings_scene_favorite_submenu_callback, app);
|
||||
for(size_t i = 0; i < APPS_COUNT; i++) {
|
||||
const char* name = favorite_fap_get_app_name(i);
|
||||
|
||||
// Select favorite item in submenu
|
||||
if(!strcmp(name, curr_favorite_app->name_or_path)) {
|
||||
pre_select_item = i + 2;
|
||||
}
|
||||
// Add the prefix
|
||||
furi_string_reset(full_name);
|
||||
if(!strcmp(name, curr_favorite_app->name_or_path)) {
|
||||
furi_string_set_str(full_name, SELECTED_PREFIX);
|
||||
} else {
|
||||
furi_string_set_str(full_name, NOT_SELECTED_PREFIX);
|
||||
}
|
||||
furi_string_cat_str(full_name, name);
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
furi_string_get_cstr(full_name),
|
||||
i + 2,
|
||||
desktop_settings_scene_favorite_submenu_callback,
|
||||
app);
|
||||
|
||||
// Select favorite item in submenu
|
||||
if(!strcmp(name, curr_favorite_app->name_or_path)) {
|
||||
pre_select_item = i + 2;
|
||||
}
|
||||
}
|
||||
|
||||
if(pre_select_item == PRESELECTED_SPECIAL) {
|
||||
if(curr_favorite_app->name_or_path[0] == '\0') {
|
||||
pre_select_item = DEFAULT_INDEX;
|
||||
submenu_change_item_label(
|
||||
submenu,
|
||||
DEFAULT_INDEX,
|
||||
default_passport ? (PASSPORT_NAME_SELECTED) : (EXTERNAL_BROWSER_NAME_SELECTED));
|
||||
} else {
|
||||
pre_select_item = EXTERNAL_APPLICATION_INDEX;
|
||||
submenu_change_item_label(
|
||||
submenu, EXTERNAL_APPLICATION_INDEX, EXTERNAL_APPLICATION_NAME_SELECTED);
|
||||
}
|
||||
}
|
||||
|
||||
submenu_set_header(submenu, is_dummy_app ? ("Dummy Mode App") : ("Favorite App"));
|
||||
switch(favorite_id) {
|
||||
case SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftShort:
|
||||
submenu_set_header(submenu, "Left - Short");
|
||||
break;
|
||||
case SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftLong:
|
||||
submenu_set_header(submenu, "Left - Long");
|
||||
break;
|
||||
case SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightShort:
|
||||
submenu_set_header(submenu, "Right - Short");
|
||||
break;
|
||||
case SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightLong:
|
||||
submenu_set_header(submenu, "Right - Long");
|
||||
break;
|
||||
case SCENE_STATE_SET_DUMMY_APP | DummyAppLeft:
|
||||
submenu_set_header(submenu, "Left");
|
||||
break;
|
||||
case SCENE_STATE_SET_DUMMY_APP | DummyAppRight:
|
||||
submenu_set_header(submenu, "Right");
|
||||
break;
|
||||
case SCENE_STATE_SET_DUMMY_APP | DummyAppDown:
|
||||
submenu_set_header(submenu, "Down");
|
||||
break;
|
||||
case SCENE_STATE_SET_DUMMY_APP | DummyAppOk:
|
||||
submenu_set_header(submenu, "Middle");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
submenu_set_selected_item(submenu, pre_select_item); // If set during loop, visual glitch.
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu);
|
||||
@@ -177,6 +228,8 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
};
|
||||
consumed = true;
|
||||
|
||||
DESKTOP_SETTINGS_SAVE(&app->settings);
|
||||
}
|
||||
|
||||
furi_string_free(temp_path);
|
||||
@@ -185,6 +238,5 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e
|
||||
|
||||
void desktop_settings_scene_favorite_on_exit(void* context) {
|
||||
DesktopSettingsApp* app = context;
|
||||
DESKTOP_SETTINGS_SAVE(&app->settings);
|
||||
submenu_reset(app->submenu);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ void desktop_settings_scene_pin_disable_on_enter(void* context) {
|
||||
popup_set_context(app->popup, app);
|
||||
popup_set_callback(app->popup, pin_disable_back_callback);
|
||||
popup_set_icon(app->popup, 0, 2, &I_DolphinMafia_119x62);
|
||||
popup_set_header(app->popup, "PIN\nDeleted!", 100, 0, AlignCenter, AlignTop);
|
||||
popup_set_header(app->popup, "Removed", 100, 10, AlignCenter, AlignTop);
|
||||
popup_set_timeout(app->popup, 1500);
|
||||
popup_enable_timeout(app->popup);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewIdPopup);
|
||||
|
||||
@@ -37,14 +37,14 @@ void desktop_settings_scene_pin_menu_on_enter(void* context) {
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Disable",
|
||||
"Remove PIN",
|
||||
SCENE_EVENT_DISABLE_PIN,
|
||||
desktop_settings_scene_pin_menu_submenu_callback,
|
||||
app);
|
||||
}
|
||||
|
||||
submenu_set_header(app->submenu, "PIN Code Settings");
|
||||
submenu_set_selected_item(app->submenu, app->menu_idx);
|
||||
submenu_set_selected_item(app->submenu, app->pin_menu_idx);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu);
|
||||
}
|
||||
|
||||
@@ -76,11 +76,16 @@ bool desktop_settings_scene_pin_menu_on_event(void* context, SceneManagerEvent e
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
submenu_set_selected_item(app->submenu, 0);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void desktop_settings_scene_pin_menu_on_exit(void* context) {
|
||||
DesktopSettingsApp* app = context;
|
||||
|
||||
app->pin_menu_idx = submenu_get_selected_item(app->submenu);
|
||||
submenu_reset(app->submenu);
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ bool desktop_settings_scene_pin_setup_on_event(void* context, SceneManagerEvent
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ void desktop_settings_scene_pin_setup_done_on_enter(void* context) {
|
||||
DESKTOP_SETTINGS_SAVE(&app->settings);
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
notification_message(notification, &sequence_single_vibro);
|
||||
notification_message(notification, &sequence_blink_green_10);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
|
||||
desktop_view_pin_input_set_context(app->pin_input_view, app);
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
#include <gui/scene_manager.h>
|
||||
#include <applications.h>
|
||||
|
||||
#include "../desktop_settings_app.h"
|
||||
#include "desktop_settings_scene.h"
|
||||
#include "desktop_settings_scene_i.h"
|
||||
|
||||
enum QuickAppsSubmenuIndex {
|
||||
QuickAppsSubmenuIndexFavoriteLeftClick,
|
||||
QuickAppsSubmenuIndexFavoriteRightClick,
|
||||
QuickAppsSubmenuIndexFavoriteLeftHold,
|
||||
QuickAppsSubmenuIndexFavoriteRightHold,
|
||||
QuickAppsSubmenuIndexDummyLeftClick,
|
||||
QuickAppsSubmenuIndexDummyRightClick,
|
||||
QuickAppsSubmenuIndexDummyDownClick,
|
||||
QuickAppsSubmenuIndexDummyMiddleClick,
|
||||
};
|
||||
|
||||
static void desktop_settings_scene_quick_apps_direction_menu_submenu_callback(
|
||||
void* context,
|
||||
uint32_t index) {
|
||||
DesktopSettingsApp* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void desktop_settings_scene_quick_apps_direction_menu_on_enter(void* context) {
|
||||
DesktopSettingsApp* app = context;
|
||||
Submenu* submenu = app->submenu;
|
||||
submenu_reset(submenu);
|
||||
|
||||
uint32_t favorite_id = scene_manager_get_scene_state(
|
||||
app->scene_manager, DesktopSettingsAppSceneQuickAppsDirectionMenu);
|
||||
|
||||
if(favorite_id == SCENE_STATE_SET_FAVORITE_APP) {
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Left - Click",
|
||||
QuickAppsSubmenuIndexFavoriteLeftClick,
|
||||
desktop_settings_scene_quick_apps_direction_menu_submenu_callback,
|
||||
app);
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Right - Click",
|
||||
QuickAppsSubmenuIndexFavoriteRightClick,
|
||||
desktop_settings_scene_quick_apps_direction_menu_submenu_callback,
|
||||
app);
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Left - Hold",
|
||||
QuickAppsSubmenuIndexFavoriteLeftHold,
|
||||
desktop_settings_scene_quick_apps_direction_menu_submenu_callback,
|
||||
app);
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Right - Hold",
|
||||
QuickAppsSubmenuIndexFavoriteRightHold,
|
||||
desktop_settings_scene_quick_apps_direction_menu_submenu_callback,
|
||||
app);
|
||||
|
||||
submenu_set_header(app->submenu, "Default Mode");
|
||||
} else {
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Left - Click",
|
||||
QuickAppsSubmenuIndexDummyLeftClick,
|
||||
desktop_settings_scene_quick_apps_direction_menu_submenu_callback,
|
||||
app);
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Right - Click",
|
||||
QuickAppsSubmenuIndexDummyRightClick,
|
||||
desktop_settings_scene_quick_apps_direction_menu_submenu_callback,
|
||||
app);
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Down - Click",
|
||||
QuickAppsSubmenuIndexDummyDownClick,
|
||||
desktop_settings_scene_quick_apps_direction_menu_submenu_callback,
|
||||
app);
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Middle - Click",
|
||||
QuickAppsSubmenuIndexDummyMiddleClick,
|
||||
desktop_settings_scene_quick_apps_direction_menu_submenu_callback,
|
||||
app);
|
||||
|
||||
submenu_set_header(app->submenu, "Dummy Mode");
|
||||
}
|
||||
|
||||
submenu_set_selected_item(app->submenu, app->quick_apps_direction_menu_idx);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu);
|
||||
}
|
||||
|
||||
bool desktop_settings_scene_quick_apps_direction_menu_on_event(
|
||||
void* context,
|
||||
SceneManagerEvent event) {
|
||||
DesktopSettingsApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case QuickAppsSubmenuIndexFavoriteLeftClick:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
DesktopSettingsAppSceneFavorite,
|
||||
SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftShort);
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
|
||||
consumed = true;
|
||||
break;
|
||||
case QuickAppsSubmenuIndexFavoriteRightClick:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
DesktopSettingsAppSceneFavorite,
|
||||
SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightShort);
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
|
||||
consumed = true;
|
||||
break;
|
||||
case QuickAppsSubmenuIndexFavoriteLeftHold:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
DesktopSettingsAppSceneFavorite,
|
||||
SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftLong);
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
|
||||
consumed = true;
|
||||
break;
|
||||
case QuickAppsSubmenuIndexFavoriteRightHold:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
DesktopSettingsAppSceneFavorite,
|
||||
SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightLong);
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
|
||||
consumed = true;
|
||||
break;
|
||||
case QuickAppsSubmenuIndexDummyLeftClick:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
DesktopSettingsAppSceneFavorite,
|
||||
SCENE_STATE_SET_DUMMY_APP | DummyAppLeft);
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
|
||||
consumed = true;
|
||||
break;
|
||||
case QuickAppsSubmenuIndexDummyRightClick:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
DesktopSettingsAppSceneFavorite,
|
||||
SCENE_STATE_SET_DUMMY_APP | DummyAppRight);
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
|
||||
consumed = true;
|
||||
break;
|
||||
case QuickAppsSubmenuIndexDummyDownClick:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
DesktopSettingsAppSceneFavorite,
|
||||
SCENE_STATE_SET_DUMMY_APP | DummyAppDown);
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
|
||||
consumed = true;
|
||||
break;
|
||||
case QuickAppsSubmenuIndexDummyMiddleClick:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
DesktopSettingsAppSceneFavorite,
|
||||
SCENE_STATE_SET_DUMMY_APP | DummyAppOk);
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
|
||||
consumed = true;
|
||||
break;
|
||||
default:
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
submenu_set_selected_item(app->submenu, 0);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void desktop_settings_scene_quick_apps_direction_menu_on_exit(void* context) {
|
||||
DesktopSettingsApp* app = context;
|
||||
app->quick_apps_direction_menu_idx = submenu_get_selected_item(app->submenu);
|
||||
submenu_reset(app->submenu);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#include <gui/scene_manager.h>
|
||||
#include <applications.h>
|
||||
|
||||
#include "../desktop_settings_app.h"
|
||||
#include "desktop_settings_scene.h"
|
||||
#include "desktop_settings_scene_i.h"
|
||||
|
||||
#define SCENE_EVENT_SET_DEFAULT (0U)
|
||||
#define SCENE_EVENT_SET_DUMMY (1U)
|
||||
|
||||
static void
|
||||
desktop_settings_scene_quick_apps_menu_submenu_callback(void* context, uint32_t index) {
|
||||
DesktopSettingsApp* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void desktop_settings_scene_quick_apps_menu_on_enter(void* context) {
|
||||
DesktopSettingsApp* app = context;
|
||||
Submenu* submenu = app->submenu;
|
||||
submenu_reset(submenu);
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Default Mode",
|
||||
SCENE_EVENT_SET_DEFAULT,
|
||||
desktop_settings_scene_quick_apps_menu_submenu_callback,
|
||||
app);
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Dummy Mode",
|
||||
SCENE_EVENT_SET_DUMMY,
|
||||
desktop_settings_scene_quick_apps_menu_submenu_callback,
|
||||
app);
|
||||
|
||||
submenu_set_header(app->submenu, "Set Quick Access Apps");
|
||||
submenu_set_selected_item(app->submenu, app->quick_apps_menu_idx);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu);
|
||||
}
|
||||
|
||||
bool desktop_settings_scene_quick_apps_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
DesktopSettingsApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case SCENE_EVENT_SET_DEFAULT:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
DesktopSettingsAppSceneQuickAppsDirectionMenu,
|
||||
SCENE_STATE_SET_FAVORITE_APP);
|
||||
scene_manager_next_scene(
|
||||
app->scene_manager, DesktopSettingsAppSceneQuickAppsDirectionMenu);
|
||||
consumed = true;
|
||||
break;
|
||||
case SCENE_EVENT_SET_DUMMY:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
DesktopSettingsAppSceneQuickAppsDirectionMenu,
|
||||
SCENE_STATE_SET_DUMMY_APP);
|
||||
scene_manager_next_scene(
|
||||
app->scene_manager, DesktopSettingsAppSceneQuickAppsDirectionMenu);
|
||||
consumed = true;
|
||||
break;
|
||||
default:
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
submenu_set_selected_item(app->submenu, 0);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void desktop_settings_scene_quick_apps_menu_on_exit(void* context) {
|
||||
DesktopSettingsApp* app = context;
|
||||
app->quick_apps_menu_idx = submenu_get_selected_item(app->submenu);
|
||||
submenu_reset(app->submenu);
|
||||
}
|
||||
@@ -9,14 +9,7 @@ typedef enum {
|
||||
DesktopSettingsPinSetup = 0,
|
||||
DesktopSettingsAutoLockDelay,
|
||||
DesktopSettingsClockDisplay,
|
||||
DesktopSettingsFavoriteLeftShort,
|
||||
DesktopSettingsFavoriteLeftLong,
|
||||
DesktopSettingsFavoriteRightShort,
|
||||
DesktopSettingsFavoriteRightLong,
|
||||
DesktopSettingsDummyLeft,
|
||||
DesktopSettingsDummyRight,
|
||||
DesktopSettingsDummyDown,
|
||||
DesktopSettingsDummyOk,
|
||||
DesktopSettingsFavoriteApps,
|
||||
} DesktopSettingsEntry;
|
||||
|
||||
#define AUTO_LOCK_DELAY_COUNT 6
|
||||
@@ -93,15 +86,7 @@ void desktop_settings_scene_start_on_enter(void* context) {
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, clock_enable_text[value_index]);
|
||||
|
||||
variable_item_list_add(variable_item_list, "Favorite App - Left Short", 1, NULL, NULL);
|
||||
variable_item_list_add(variable_item_list, "Favorite App - Left Long", 1, NULL, NULL);
|
||||
variable_item_list_add(variable_item_list, "Favorite App - Right Short", 1, NULL, NULL);
|
||||
variable_item_list_add(variable_item_list, "Favorite App - Right Long", 1, NULL, NULL);
|
||||
|
||||
variable_item_list_add(variable_item_list, "Dummy Mode App - Left", 1, NULL, NULL);
|
||||
variable_item_list_add(variable_item_list, "Dummy Mode App - Right", 1, NULL, NULL);
|
||||
variable_item_list_add(variable_item_list, "Dummy Mode App - Down", 1, NULL, NULL);
|
||||
variable_item_list_add(variable_item_list, "Dummy Mode App - Ok", 1, NULL, NULL);
|
||||
variable_item_list_add(variable_item_list, "Set Quick Access Apps", 1, NULL, NULL);
|
||||
|
||||
variable_item_list_set_enter_callback(
|
||||
variable_item_list, desktop_settings_scene_start_var_list_enter_callback, app);
|
||||
@@ -119,62 +104,8 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinMenu);
|
||||
break;
|
||||
|
||||
case DesktopSettingsFavoriteLeftShort:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
DesktopSettingsAppSceneFavorite,
|
||||
SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftShort);
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
|
||||
break;
|
||||
case DesktopSettingsFavoriteLeftLong:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
DesktopSettingsAppSceneFavorite,
|
||||
SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftLong);
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
|
||||
break;
|
||||
case DesktopSettingsFavoriteRightShort:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
DesktopSettingsAppSceneFavorite,
|
||||
SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightShort);
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
|
||||
break;
|
||||
case DesktopSettingsFavoriteRightLong:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
DesktopSettingsAppSceneFavorite,
|
||||
SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightLong);
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
|
||||
break;
|
||||
|
||||
case DesktopSettingsDummyLeft:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
DesktopSettingsAppSceneFavorite,
|
||||
SCENE_STATE_SET_DUMMY_APP | DummyAppLeft);
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
|
||||
break;
|
||||
case DesktopSettingsDummyRight:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
DesktopSettingsAppSceneFavorite,
|
||||
SCENE_STATE_SET_DUMMY_APP | DummyAppRight);
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
|
||||
break;
|
||||
case DesktopSettingsDummyDown:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
DesktopSettingsAppSceneFavorite,
|
||||
SCENE_STATE_SET_DUMMY_APP | DummyAppDown);
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
|
||||
break;
|
||||
case DesktopSettingsDummyOk:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
DesktopSettingsAppSceneFavorite,
|
||||
SCENE_STATE_SET_DUMMY_APP | DummyAppOk);
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
|
||||
case DesktopSettingsFavoriteApps:
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneQuickAppsMenu);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -188,5 +119,4 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even
|
||||
void desktop_settings_scene_start_on_exit(void* context) {
|
||||
DesktopSettingsApp* app = context;
|
||||
variable_item_list_reset(app->variable_item_list);
|
||||
DESKTOP_SETTINGS_SAVE(&app->settings);
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ static void desktop_settings_view_pin_setup_howto2_draw(Canvas* canvas, void* mo
|
||||
elements_multiline_text_aligned(
|
||||
canvas,
|
||||
64,
|
||||
24,
|
||||
AlignCenter,
|
||||
0,
|
||||
AlignCenter,
|
||||
AlignTop,
|
||||
"Forgotten PIN can only be\n"
|
||||
"reset with entire device.\n"
|
||||
"Read docs How to reset PIN.");
|
||||
|
||||
@@ -30,3 +30,5 @@ typedef enum {
|
||||
PowerSettingsAppViewSubmenu,
|
||||
PowerSettingsAppViewDialog,
|
||||
} PowerSettingsAppView;
|
||||
|
||||
typedef enum { RebootTypeDFU, RebootTypeNormal } RebootType;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
ADD_SCENE(power_settings, start, Start)
|
||||
ADD_SCENE(power_settings, battery_info, BatteryInfo)
|
||||
ADD_SCENE(power_settings, reboot, Reboot)
|
||||
ADD_SCENE(power_settings, reboot_confirm, RebootConfirm)
|
||||
ADD_SCENE(power_settings, power_off, PowerOff)
|
||||
|
||||
@@ -10,12 +10,12 @@ void power_settings_scene_power_off_on_enter(void* context) {
|
||||
PowerSettingsApp* app = context;
|
||||
DialogEx* dialog = app->dialog;
|
||||
|
||||
dialog_ex_set_header(dialog, "Turn OFF Device?", 64, 2, AlignCenter, AlignTop);
|
||||
dialog_ex_set_header(dialog, "Turn Off Device?", 64, 0, AlignCenter, AlignTop);
|
||||
dialog_ex_set_text(
|
||||
dialog, " I will be\nwaiting for\n you here...", 78, 16, AlignLeft, AlignTop);
|
||||
dialog_ex_set_icon(dialog, 21, 13, &I_Cry_dolph_55x52);
|
||||
dialog_ex_set_left_button_text(dialog, "Back");
|
||||
dialog_ex_set_right_button_text(dialog, "OFF");
|
||||
dialog, " I will be\nwaiting for\n you here...", 78, 14, AlignLeft, AlignTop);
|
||||
dialog_ex_set_icon(dialog, 14, 10, &I_dolph_cry_49x54);
|
||||
dialog_ex_set_left_button_text(dialog, "Cancel");
|
||||
dialog_ex_set_right_button_text(dialog, "Power Off");
|
||||
dialog_ex_set_result_callback(dialog, power_settings_scene_power_off_dialog_callback);
|
||||
dialog_ex_set_context(dialog, app);
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ void power_settings_scene_reboot_on_enter(void* context) {
|
||||
app);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Flipper OS",
|
||||
"Reboot Flipper",
|
||||
PowerSettingsRebootSubmenuIndexOs,
|
||||
power_settings_scene_reboot_submenu_callback,
|
||||
app);
|
||||
@@ -33,14 +33,18 @@ void power_settings_scene_reboot_on_enter(void* context) {
|
||||
}
|
||||
|
||||
bool power_settings_scene_reboot_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
PowerSettingsApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == PowerSettingsRebootSubmenuIndexDfu) {
|
||||
power_reboot(PowerBootModeDfu);
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, PowerSettingsAppSceneRebootConfirm, RebootTypeDFU);
|
||||
scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneRebootConfirm);
|
||||
} else if(event.event == PowerSettingsRebootSubmenuIndexOs) {
|
||||
power_reboot(PowerBootModeNormal);
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, PowerSettingsAppSceneRebootConfirm, RebootTypeNormal);
|
||||
scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneRebootConfirm);
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "../power_settings_app.h"
|
||||
|
||||
void power_settings_scene_reboot_confirm_dialog_callback(DialogExResult result, void* context) {
|
||||
furi_assert(context);
|
||||
PowerSettingsApp* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||
}
|
||||
|
||||
void power_settings_scene_reboot_confirm_on_enter(void* context) {
|
||||
PowerSettingsApp* app = context;
|
||||
DialogEx* dialog = app->dialog;
|
||||
|
||||
RebootType reboot_type =
|
||||
scene_manager_get_scene_state(app->scene_manager, PowerSettingsAppSceneRebootConfirm);
|
||||
|
||||
if(reboot_type == RebootTypeDFU) {
|
||||
dialog_ex_set_header(dialog, "Reboot to DFU Mode?", 64, 0, AlignCenter, AlignTop);
|
||||
dialog_ex_set_text(
|
||||
dialog,
|
||||
"Needed for device maintenance\nor firmware upgrades",
|
||||
64,
|
||||
14,
|
||||
AlignCenter,
|
||||
AlignTop);
|
||||
} else if(reboot_type == RebootTypeNormal) {
|
||||
dialog_ex_set_header(dialog, "Reboot Flipper?", 64, 0, AlignCenter, AlignTop);
|
||||
dialog_ex_set_text(
|
||||
dialog, "May help with some firmware\n issues", 64, 14, AlignCenter, AlignTop);
|
||||
} else {
|
||||
furi_crash("Invalid reboot type");
|
||||
}
|
||||
|
||||
dialog_ex_set_left_button_text(dialog, "Cancel");
|
||||
dialog_ex_set_right_button_text(dialog, "Reboot");
|
||||
|
||||
dialog_ex_set_result_callback(dialog, power_settings_scene_reboot_confirm_dialog_callback);
|
||||
dialog_ex_set_context(dialog, app);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, PowerSettingsAppViewDialog);
|
||||
}
|
||||
|
||||
bool power_settings_scene_reboot_confirm_on_event(void* context, SceneManagerEvent event) {
|
||||
PowerSettingsApp* app = context;
|
||||
bool consumed = false;
|
||||
RebootType reboot_type =
|
||||
scene_manager_get_scene_state(app->scene_manager, PowerSettingsAppSceneRebootConfirm);
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == DialogExResultLeft) {
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
} else if(event.event == DialogExResultRight) {
|
||||
if(reboot_type == RebootTypeDFU) {
|
||||
power_reboot(PowerBootModeDfu);
|
||||
} else {
|
||||
power_reboot(PowerBootModeNormal);
|
||||
}
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void power_settings_scene_reboot_confirm_on_exit(void* context) {
|
||||
PowerSettingsApp* app = context;
|
||||
dialog_ex_reset(app->dialog);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "../storage_settings.h"
|
||||
#include <furi_hal.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#define BENCH_DATA_SIZE 4096
|
||||
#define BENCH_COUNT 6
|
||||
@@ -86,7 +88,8 @@ static void storage_settings_scene_benchmark(StorageSettings* app) {
|
||||
uint32_t bench_w_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0};
|
||||
uint32_t bench_r_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0};
|
||||
|
||||
dialog_ex_set_header(dialog_ex, "Benchmarking...", 64, 32, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_header(dialog_ex, "Benchmarking...", 74, 32, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_icon(dialog_ex, 12, 20, &I_LoadingHourglass_24x24);
|
||||
for(size_t i = 0; i < BENCH_COUNT; i++) {
|
||||
if(!storage_settings_scene_bench_write(
|
||||
app->fs_api, bench_size[i], bench_data, &bench_w_speed[i]))
|
||||
@@ -95,6 +98,7 @@ static void storage_settings_scene_benchmark(StorageSettings* app) {
|
||||
if(i > 0) furi_string_cat_printf(app->text_string, "\n");
|
||||
furi_string_cat_printf(app->text_string, "%ub : W %luK ", bench_size[i], bench_w_speed[i]);
|
||||
dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex, furi_string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter);
|
||||
|
||||
@@ -110,6 +114,12 @@ static void storage_settings_scene_benchmark(StorageSettings* app) {
|
||||
dialog_ex, furi_string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter);
|
||||
}
|
||||
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
notification_message(notification, &sequence_single_vibro);
|
||||
notification_message(notification, &sequence_set_green_255);
|
||||
notification_message(notification, &sequence_success);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
|
||||
free(bench_data);
|
||||
}
|
||||
|
||||
@@ -146,11 +156,17 @@ bool storage_settings_scene_benchmark_on_event(void* context, SceneManagerEvent
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DialogExResultCenter:
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, StorageSettingsStart);
|
||||
break;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack && sd_status != FSE_OK) {
|
||||
consumed = true;
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
if(sd_status == FSE_OK) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, StorageSettingsStart);
|
||||
} else {
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
@@ -160,6 +176,10 @@ void storage_settings_scene_benchmark_on_exit(void* context) {
|
||||
StorageSettings* app = context;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
notification_message(notification, &sequence_reset_green);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
|
||||
dialog_ex_reset(dialog_ex);
|
||||
|
||||
furi_string_reset(app->text_string);
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#include "../storage_settings.h"
|
||||
|
||||
static void
|
||||
storage_settings_scene_benchmark_confirm_dialog_callback(DialogExResult result, void* context) {
|
||||
StorageSettings* app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||
}
|
||||
|
||||
void storage_settings_scene_benchmark_confirm_on_enter(void* context) {
|
||||
StorageSettings* app = context;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
FS_Error sd_status = storage_sd_status(app->fs_api);
|
||||
|
||||
if(sd_status == FSE_NOT_READY) {
|
||||
dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42);
|
||||
dialog_ex_set_header(dialog_ex, "SD Card Not Mounted", 64, 3, AlignCenter, AlignTop);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop);
|
||||
dialog_ex_set_center_button_text(dialog_ex, "Ok");
|
||||
} else {
|
||||
dialog_ex_set_header(dialog_ex, "Benchmark SD Card?", 64, 0, AlignCenter, AlignTop);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex,
|
||||
"SD will be tested in SPI\nmode. Learn more:\nr.flipper.net/sd_test",
|
||||
0,
|
||||
12,
|
||||
AlignLeft,
|
||||
AlignTop);
|
||||
dialog_ex_set_icon(dialog_ex, 103, 12, &I_qr_benchmark_25x25);
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Cancel");
|
||||
dialog_ex_set_right_button_text(dialog_ex, "Benchmark");
|
||||
}
|
||||
|
||||
dialog_ex_set_context(dialog_ex, app);
|
||||
dialog_ex_set_result_callback(
|
||||
dialog_ex, storage_settings_scene_benchmark_confirm_dialog_callback);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx);
|
||||
}
|
||||
|
||||
bool storage_settings_scene_benchmark_confirm_on_event(void* context, SceneManagerEvent event) {
|
||||
StorageSettings* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DialogExResultLeft:
|
||||
case DialogExResultCenter:
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
break;
|
||||
case DialogExResultRight:
|
||||
scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmark);
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void storage_settings_scene_benchmark_confirm_on_exit(void* context) {
|
||||
StorageSettings* app = context;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_reset(dialog_ex);
|
||||
}
|
||||
@@ -5,5 +5,6 @@ ADD_SCENE(storage_settings, format_confirm, FormatConfirm)
|
||||
ADD_SCENE(storage_settings, formatting, Formatting)
|
||||
ADD_SCENE(storage_settings, sd_info, SDInfo)
|
||||
ADD_SCENE(storage_settings, internal_info, InternalInfo)
|
||||
ADD_SCENE(storage_settings, benchmark_confirm, BenchmarkConfirm)
|
||||
ADD_SCENE(storage_settings, benchmark, Benchmark)
|
||||
ADD_SCENE(storage_settings, factory_reset, FactoryReset)
|
||||
|
||||
@@ -21,14 +21,14 @@ void storage_settings_scene_factory_reset_on_enter(void* context) {
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Cancel");
|
||||
dialog_ex_set_right_button_text(dialog_ex, "Erase");
|
||||
|
||||
dialog_ex_set_header(dialog_ex, "Confirm Factory Reset", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_header(dialog_ex, "Confirm Factory Reset?", 64, 0, AlignCenter, AlignTop);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex,
|
||||
"Internal storage will be erased\r\nData and settings will be lost!",
|
||||
"Internal storage will be erased\ndata and settings will be lost!",
|
||||
64,
|
||||
32,
|
||||
14,
|
||||
AlignCenter,
|
||||
AlignCenter);
|
||||
AlignTop);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx);
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ void storage_settings_scene_format_confirm_on_enter(void* context) {
|
||||
dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop);
|
||||
dialog_ex_set_center_button_text(dialog_ex, "Ok");
|
||||
} else {
|
||||
dialog_ex_set_header(dialog_ex, "Format SD Card?", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(dialog_ex, "All data will be lost!", 64, 32, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_header(dialog_ex, "Format SD Card?", 64, 0, AlignCenter, AlignTop);
|
||||
dialog_ex_set_text(dialog_ex, "All data will be lost!", 64, 12, AlignCenter, AlignTop);
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Cancel");
|
||||
dialog_ex_set_right_button_text(dialog_ex, "Format");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "../storage_settings.h"
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
static const NotificationMessage message_green_165 = {
|
||||
.type = NotificationMessageTypeLedGreen,
|
||||
@@ -31,7 +33,8 @@ void storage_settings_scene_formatting_on_enter(void* context) {
|
||||
FS_Error error;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_set_header(dialog_ex, "Formatting...", 64, 32, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_header(dialog_ex, "Formatting...", 70, 32, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_icon(dialog_ex, 15, 20, &I_LoadingHourglass_24x24);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx);
|
||||
|
||||
notification_message_block(app->notification, &sequence_set_formatting_leds);
|
||||
@@ -44,13 +47,19 @@ void storage_settings_scene_formatting_on_enter(void* context) {
|
||||
|
||||
if(error != FSE_OK) {
|
||||
dialog_ex_set_header(dialog_ex, "Cannot Format SD Card", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter);
|
||||
} else {
|
||||
dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42);
|
||||
dialog_ex_set_header(dialog_ex, "Format\ncomplete!", 14, 15, AlignLeft, AlignTop);
|
||||
dialog_ex_set_icon(dialog_ex, 48, 6, &I_DolphinDone_80x58);
|
||||
dialog_ex_set_header(dialog_ex, "Formatted", 5, 10, AlignLeft, AlignTop);
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
notification_message(notification, &sequence_single_vibro);
|
||||
notification_message(notification, &sequence_set_green_255);
|
||||
notification_message(notification, &sequence_success);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
}
|
||||
dialog_ex_set_center_button_text(dialog_ex, "OK");
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Finish");
|
||||
}
|
||||
|
||||
bool storage_settings_scene_formatting_on_event(void* context, SceneManagerEvent event) {
|
||||
@@ -59,7 +68,7 @@ bool storage_settings_scene_formatting_on_event(void* context, SceneManagerEvent
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DialogExResultCenter:
|
||||
case DialogExResultLeft:
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, StorageSettingsStart);
|
||||
break;
|
||||
@@ -75,5 +84,9 @@ void storage_settings_scene_formatting_on_exit(void* context) {
|
||||
StorageSettings* app = context;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
notification_message(notification, &sequence_reset_green);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
|
||||
dialog_ex_reset(dialog_ex);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ void storage_settings_scene_internal_info_on_enter(void* context) {
|
||||
} else {
|
||||
furi_string_printf(
|
||||
app->text_string,
|
||||
"Label: %s\nType: LittleFS\n%lu KiB total\n%lu KiB free",
|
||||
"Name: %s\nType: LittleFS\nTotal: %lu KiB\nFree: %lu KiB",
|
||||
furi_hal_version_get_name_ptr() ? furi_hal_version_get_name_ptr() : "Unknown",
|
||||
(uint32_t)(total_space / 1024),
|
||||
(uint32_t)(free_space / 1024));
|
||||
|
||||
@@ -27,12 +27,31 @@ void storage_settings_scene_sd_info_on_enter(void* context) {
|
||||
} else {
|
||||
furi_string_printf(
|
||||
app->text_string,
|
||||
"Label: %s\nType: %s\n%lu KiB total\n%lu KiB free\n"
|
||||
"%02X%s %s v%i.%i\nSN:%04lX %02i/%i",
|
||||
"Label: %s\nType: %s\n",
|
||||
sd_info.label,
|
||||
sd_api_get_fs_type_text(sd_info.fs_type),
|
||||
sd_info.kb_total,
|
||||
sd_info.kb_free,
|
||||
sd_api_get_fs_type_text(sd_info.fs_type));
|
||||
|
||||
if(sd_info.kb_total < 1024) {
|
||||
furi_string_cat_printf(app->text_string, "Total: %lu KiB\n", sd_info.kb_total);
|
||||
} else if(sd_info.kb_total < 1024 * 1024) {
|
||||
furi_string_cat_printf(app->text_string, "Total: %lu MiB\n", sd_info.kb_total / 1024);
|
||||
} else {
|
||||
furi_string_cat_printf(
|
||||
app->text_string, "Total: %lu GiB\n", sd_info.kb_total / (1024 * 1024));
|
||||
}
|
||||
|
||||
if(sd_info.kb_free < 1024) {
|
||||
furi_string_cat_printf(app->text_string, "Free: %lu KiB\n", sd_info.kb_free);
|
||||
} else if(sd_info.kb_free < 1024 * 1024) {
|
||||
furi_string_cat_printf(app->text_string, "Free: %lu MiB\n", sd_info.kb_free / 1024);
|
||||
} else {
|
||||
furi_string_cat_printf(
|
||||
app->text_string, "Free: %lu GiB\n", sd_info.kb_free / (1024 * 1024));
|
||||
}
|
||||
|
||||
furi_string_cat_printf(
|
||||
app->text_string,
|
||||
"%02X%s %s v%i.%i\nSN:%04lX %02i/%i",
|
||||
sd_info.manufacturer_id,
|
||||
sd_info.oem_id,
|
||||
sd_info.product_name,
|
||||
@@ -41,6 +60,7 @@ void storage_settings_scene_sd_info_on_enter(void* context) {
|
||||
sd_info.product_serial_number,
|
||||
sd_info.manufacturing_month,
|
||||
sd_info.manufacturing_year);
|
||||
|
||||
dialog_ex_set_text(
|
||||
dialog_ex, furi_string_get_cstr(app->text_string), 4, 1, AlignLeft, AlignTop);
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ bool storage_settings_scene_start_on_event(void* context, SceneManagerEvent even
|
||||
case StorageSettingsStartSubmenuIndexBenchy:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexBenchy);
|
||||
scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmark);
|
||||
scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmarkConfirm);
|
||||
consumed = true;
|
||||
break;
|
||||
case StorageSettingsStartSubmenuIndexFactoryReset:
|
||||
|
||||
@@ -46,3 +46,27 @@ App(
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_serial.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_submenu",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_submenu_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_submenu.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_math",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_math_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_math.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_textbox",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_textbox_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_textbox.c"],
|
||||
)
|
||||
|
||||
69
applications/system/js_app/examples/apps/Scripts/math.js
Normal file
69
applications/system/js_app/examples/apps/Scripts/math.js
Normal file
@@ -0,0 +1,69 @@
|
||||
let math = require("math");
|
||||
|
||||
print("math.abs(-5):", math.abs(-5));
|
||||
print("math.acos(0.5):", math.acos(0.5));
|
||||
print("math.acosh(2):", math.acosh(2));
|
||||
print("math.asin(0.5):", math.asin(0.5));
|
||||
print("math.asinh(2):", math.asinh(2));
|
||||
print("math.atan(1):", math.atan(1));
|
||||
print("math.atan2(1, 1):", math.atan2(1, 1));
|
||||
print("math.atanh(0.5):", math.atanh(0.5));
|
||||
print("math.cbrt(27):", math.cbrt(27));
|
||||
print("math.ceil(5.3):", math.ceil(5.3));
|
||||
print("math.clz32(1):", math.clz32(1));
|
||||
print("math.cos(math.PI):", math.cos(math.PI));
|
||||
print("math.exp(1):", math.exp(1));
|
||||
print("math.floor(5.7):", math.floor(5.7));
|
||||
print("math.max(3, 5):", math.max(3, 5));
|
||||
print("math.min(3, 5):", math.min(3, 5));
|
||||
print("math.pow(2, 3):", math.pow(2, 3));
|
||||
print("math.random():", math.random());
|
||||
print("math.sign(-5):", math.sign(-5));
|
||||
print("math.sin(math.PI/2):", math.sin(math.PI / 2));
|
||||
print("math.sqrt(25):", math.sqrt(25));
|
||||
print("math.trunc(5.7):", math.trunc(5.7));
|
||||
|
||||
// Unit tests. Please add more if you have time and knowledge.
|
||||
// math.EPSILON on Flipper Zero is 2.22044604925031308085e-16
|
||||
|
||||
let succeeded = 0;
|
||||
let failed = 0;
|
||||
|
||||
function test(text, result, expected, epsilon) {
|
||||
let is_equal = math.is_equal(result, expected, epsilon);
|
||||
if (is_equal) {
|
||||
succeeded += 1;
|
||||
} else {
|
||||
failed += 1;
|
||||
print(text, "expected", expected, "got", result);
|
||||
}
|
||||
}
|
||||
|
||||
test("math.abs(5)", math.abs(-5), 5, math.EPSILON);
|
||||
test("math.abs(0.5)", math.abs(-0.5), 0.5, math.EPSILON);
|
||||
test("math.abs(5)", math.abs(5), 5, math.EPSILON);
|
||||
test("math.abs(-0.5)", math.abs(0.5), 0.5, math.EPSILON);
|
||||
test("math.acos(0.5)", math.acos(0.5), 1.0471975511965976, math.EPSILON);
|
||||
test("math.acosh(2)", math.acosh(2), 1.3169578969248166, math.EPSILON);
|
||||
test("math.asin(0.5)", math.asin(0.5), 0.5235987755982988, math.EPSILON);
|
||||
test("math.asinh(2)", math.asinh(2), 1.4436354751788103, math.EPSILON);
|
||||
test("math.atan(1)", math.atan(1), 0.7853981633974483, math.EPSILON);
|
||||
test("math.atan2(1, 1)", math.atan2(1, 1), 0.7853981633974483, math.EPSILON);
|
||||
test("math.atanh(0.5)", math.atanh(0.5), 0.5493061443340549, math.EPSILON);
|
||||
test("math.cbrt(27)", math.cbrt(27), 3, math.EPSILON);
|
||||
test("math.ceil(5.3)", math.ceil(5.3), 6, math.EPSILON);
|
||||
test("math.clz32(1)", math.clz32(1), 31, math.EPSILON);
|
||||
test("math.floor(5.7)", math.floor(5.7), 5, math.EPSILON);
|
||||
test("math.max(3, 5)", math.max(3, 5), 5, math.EPSILON);
|
||||
test("math.min(3, 5)", math.min(3, 5), 3, math.EPSILON);
|
||||
test("math.pow(2, 3)", math.pow(2, 3), 8, math.EPSILON);
|
||||
test("math.sign(-5)", math.sign(-5), -1, math.EPSILON);
|
||||
test("math.sqrt(25)", math.sqrt(25), 5, math.EPSILON);
|
||||
test("math.trunc(5.7)", math.trunc(5.7), 5, math.EPSILON);
|
||||
test("math.cos(math.PI)", math.cos(math.PI), -1, math.EPSILON * 18); // Error 3.77475828372553223744e-15
|
||||
test("math.exp(1)", math.exp(1), 2.718281828459045, math.EPSILON * 2); // Error 4.44089209850062616169e-16
|
||||
test("math.sin(math.PI / 2)", math.sin(math.PI / 2), 1, math.EPSILON * 4.5); // Error 9.99200722162640886381e-16
|
||||
|
||||
if (failed > 0) {
|
||||
print("!!!", failed, "Unit tests failed !!!");
|
||||
}
|
||||
11
applications/system/js_app/examples/apps/Scripts/submenu.js
Normal file
11
applications/system/js_app/examples/apps/Scripts/submenu.js
Normal file
@@ -0,0 +1,11 @@
|
||||
let submenu = require("submenu");
|
||||
|
||||
submenu.addItem("Item 1", 0);
|
||||
submenu.addItem("Item 2", 1);
|
||||
submenu.addItem("Item 3", 2);
|
||||
|
||||
submenu.setHeader("Select an option:");
|
||||
|
||||
let result = submenu.show();
|
||||
// Returns undefined when pressing back
|
||||
print("Result:", result);
|
||||
30
applications/system/js_app/examples/apps/Scripts/textbox.js
Normal file
30
applications/system/js_app/examples/apps/Scripts/textbox.js
Normal file
@@ -0,0 +1,30 @@
|
||||
let textbox = require("textbox");
|
||||
|
||||
// You should set config before adding text
|
||||
// Focus (start / end), Font (text / hex)
|
||||
textbox.setConfig("end", "text");
|
||||
|
||||
// Can make sure it's cleared before showing, in case of reusing in same script
|
||||
// (Closing textbox already clears the text, but maybe you added more in a loop for example)
|
||||
textbox.clearText();
|
||||
|
||||
// Add default text
|
||||
textbox.addText("Example dynamic updating textbox\n");
|
||||
|
||||
// Non-blocking, can keep updating text after, can close in JS or in GUI
|
||||
textbox.show();
|
||||
|
||||
let i = 0;
|
||||
while (textbox.isOpen() && i < 20) {
|
||||
print("console", i++);
|
||||
|
||||
// Add text to textbox buffer
|
||||
textbox.addText("textbox " + to_string(i) + "\n");
|
||||
|
||||
delay(500);
|
||||
}
|
||||
|
||||
// If not closed by user (instead i < 20 is false above), close forcefully
|
||||
if (textbox.isOpen()) {
|
||||
textbox.close();
|
||||
}
|
||||
@@ -285,7 +285,7 @@ static int32_t js_thread(void* arg) {
|
||||
}
|
||||
const char* stack_trace = mjs_get_stack_trace(mjs);
|
||||
if(stack_trace != NULL) {
|
||||
FURI_LOG_E(TAG, "Stack trace:\n%s", stack_trace);
|
||||
FURI_LOG_E(TAG, "Stack trace:\r\n%s", stack_trace);
|
||||
if(worker->app_callback) {
|
||||
worker->app_callback(JsThreadEventErrorTrace, stack_trace, worker->context);
|
||||
}
|
||||
|
||||
355
applications/system/js_app/modules/js_math.c
Normal file
355
applications/system/js_app/modules/js_math.c
Normal file
@@ -0,0 +1,355 @@
|
||||
#include "../js_modules.h"
|
||||
#include "furi_hal_random.h"
|
||||
#include <float.h>
|
||||
|
||||
#define JS_MATH_PI ((double)M_PI)
|
||||
#define JS_MATH_E ((double)M_E)
|
||||
#define JS_MATH_EPSILON ((double)DBL_EPSILON)
|
||||
|
||||
#define TAG "JsMath"
|
||||
|
||||
static void ret_bad_args(struct mjs* mjs, const char* error) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static bool check_args(struct mjs* mjs, size_t count) {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args != count) {
|
||||
ret_bad_args(mjs, "Wrong argument count");
|
||||
return false;
|
||||
}
|
||||
for(size_t i = 0; i < count; i++) {
|
||||
if(!mjs_is_number(mjs_arg(mjs, i))) {
|
||||
ret_bad_args(mjs, "Wrong argument type");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void js_math_is_equal(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 3)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double a = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
double b = mjs_get_double(mjs, mjs_arg(mjs, 1));
|
||||
double e = mjs_get_double(mjs, mjs_arg(mjs, 2));
|
||||
double f = fabs(a - b);
|
||||
|
||||
mjs_return(mjs, mjs_mk_boolean(mjs, (f <= e)));
|
||||
}
|
||||
|
||||
void js_math_abs(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, fabs(x)));
|
||||
}
|
||||
|
||||
void js_math_acos(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
if(x < (double)-1. || x > (double)1.) {
|
||||
ret_bad_args(mjs, "Invalid input value for math.acos");
|
||||
return;
|
||||
}
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, acos(x)));
|
||||
}
|
||||
|
||||
void js_math_acosh(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
if(x < (double)1.) {
|
||||
ret_bad_args(mjs, "Invalid input value for math.acosh");
|
||||
return;
|
||||
}
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x - (double)1.))));
|
||||
}
|
||||
|
||||
void js_math_asin(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, asin(x)));
|
||||
}
|
||||
|
||||
void js_math_asinh(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x + (double)1.))));
|
||||
}
|
||||
|
||||
void js_math_atan(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, atan(x)));
|
||||
}
|
||||
|
||||
void js_math_atan2(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 2)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double y = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 1));
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, atan2(y, x)));
|
||||
}
|
||||
|
||||
void js_math_atanh(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
if(x < (double)-1. || x > (double)1.) {
|
||||
ret_bad_args(mjs, "Invalid input value for math.atanh");
|
||||
return;
|
||||
}
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, (double)0.5 * log(((double)1. + x) / ((double)1. - x))));
|
||||
}
|
||||
|
||||
void js_math_cbrt(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, cbrt(x)));
|
||||
}
|
||||
|
||||
void js_math_ceil(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
mjs_return(mjs, mjs_mk_number(mjs, ceil(x)));
|
||||
}
|
||||
|
||||
void js_math_clz32(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int x = (unsigned int)mjs_get_int(mjs, mjs_arg(mjs, 0));
|
||||
int count = 0;
|
||||
while(x) {
|
||||
x >>= 1;
|
||||
count++;
|
||||
}
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, 32 - count));
|
||||
}
|
||||
|
||||
void js_math_cos(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, cos(x)));
|
||||
}
|
||||
|
||||
void js_math_exp(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, exp(x)));
|
||||
}
|
||||
|
||||
void js_math_floor(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, floor(x)));
|
||||
}
|
||||
|
||||
void js_math_log(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
if(x <= 0) {
|
||||
ret_bad_args(mjs, "Invalid input value for math.log");
|
||||
return;
|
||||
}
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, log(x)));
|
||||
}
|
||||
|
||||
void js_math_max(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 2)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
double y = mjs_get_double(mjs, mjs_arg(mjs, 1));
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, x > y ? x : y));
|
||||
}
|
||||
|
||||
void js_math_min(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 2)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
double y = mjs_get_double(mjs, mjs_arg(mjs, 1));
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, x < y ? x : y));
|
||||
}
|
||||
|
||||
void js_math_pow(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 2)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double base = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
double exponent = mjs_get_double(mjs, mjs_arg(mjs, 1));
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, pow(base, exponent)));
|
||||
}
|
||||
|
||||
void js_math_random(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// double clearly provides more bits for entropy then we pack
|
||||
// 32bit should be enough for now, but fix it maybe
|
||||
const uint32_t random_val = furi_hal_random_get();
|
||||
double rnd = (double)random_val / (double)FURI_HAL_RANDOM_MAX;
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, rnd));
|
||||
}
|
||||
|
||||
void js_math_sign(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
|
||||
mjs_return(
|
||||
mjs,
|
||||
mjs_mk_number(
|
||||
mjs, fabs(x) <= JS_MATH_EPSILON ? 0 : (x < (double)0. ? (double)-1.0 : (double)1.0)));
|
||||
}
|
||||
|
||||
void js_math_sin(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, sin(x)));
|
||||
}
|
||||
|
||||
void js_math_sqrt(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
if(x < (double)0.) {
|
||||
ret_bad_args(mjs, "Invalid input value for math.sqrt");
|
||||
return;
|
||||
}
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, sqrt(x)));
|
||||
}
|
||||
|
||||
void js_math_trunc(struct mjs* mjs) {
|
||||
if(!check_args(mjs, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, x < (double)0. ? ceil(x) : floor(x)));
|
||||
}
|
||||
|
||||
static void* js_math_create(struct mjs* mjs, mjs_val_t* object) {
|
||||
mjs_val_t math_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, math_obj, "is_equal", ~0, MJS_MK_FN(js_math_is_equal));
|
||||
mjs_set(mjs, math_obj, "abs", ~0, MJS_MK_FN(js_math_abs));
|
||||
mjs_set(mjs, math_obj, "acos", ~0, MJS_MK_FN(js_math_acos));
|
||||
mjs_set(mjs, math_obj, "acosh", ~0, MJS_MK_FN(js_math_acosh));
|
||||
mjs_set(mjs, math_obj, "asin", ~0, MJS_MK_FN(js_math_asin));
|
||||
mjs_set(mjs, math_obj, "asinh", ~0, MJS_MK_FN(js_math_asinh));
|
||||
mjs_set(mjs, math_obj, "atan", ~0, MJS_MK_FN(js_math_atan));
|
||||
mjs_set(mjs, math_obj, "atan2", ~0, MJS_MK_FN(js_math_atan2));
|
||||
mjs_set(mjs, math_obj, "atanh", ~0, MJS_MK_FN(js_math_atanh));
|
||||
mjs_set(mjs, math_obj, "cbrt", ~0, MJS_MK_FN(js_math_cbrt));
|
||||
mjs_set(mjs, math_obj, "ceil", ~0, MJS_MK_FN(js_math_ceil));
|
||||
mjs_set(mjs, math_obj, "clz32", ~0, MJS_MK_FN(js_math_clz32));
|
||||
mjs_set(mjs, math_obj, "cos", ~0, MJS_MK_FN(js_math_cos));
|
||||
mjs_set(mjs, math_obj, "exp", ~0, MJS_MK_FN(js_math_exp));
|
||||
mjs_set(mjs, math_obj, "floor", ~0, MJS_MK_FN(js_math_floor));
|
||||
mjs_set(mjs, math_obj, "log", ~0, MJS_MK_FN(js_math_log));
|
||||
mjs_set(mjs, math_obj, "max", ~0, MJS_MK_FN(js_math_max));
|
||||
mjs_set(mjs, math_obj, "min", ~0, MJS_MK_FN(js_math_min));
|
||||
mjs_set(mjs, math_obj, "pow", ~0, MJS_MK_FN(js_math_pow));
|
||||
mjs_set(mjs, math_obj, "random", ~0, MJS_MK_FN(js_math_random));
|
||||
mjs_set(mjs, math_obj, "sign", ~0, MJS_MK_FN(js_math_sign));
|
||||
mjs_set(mjs, math_obj, "sin", ~0, MJS_MK_FN(js_math_sin));
|
||||
mjs_set(mjs, math_obj, "sqrt", ~0, MJS_MK_FN(js_math_sqrt));
|
||||
mjs_set(mjs, math_obj, "trunc", ~0, MJS_MK_FN(js_math_trunc));
|
||||
mjs_set(mjs, math_obj, "PI", ~0, mjs_mk_number(mjs, JS_MATH_PI));
|
||||
mjs_set(mjs, math_obj, "E", ~0, mjs_mk_number(mjs, JS_MATH_E));
|
||||
mjs_set(mjs, math_obj, "EPSILON", ~0, mjs_mk_number(mjs, JS_MATH_EPSILON));
|
||||
*object = math_obj;
|
||||
return (void*)1;
|
||||
}
|
||||
|
||||
static const JsModuleDescriptor js_math_desc = {
|
||||
"math",
|
||||
js_math_create,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor plugin_descriptor = {
|
||||
.appid = PLUGIN_APP_ID,
|
||||
.ep_api_version = PLUGIN_API_VERSION,
|
||||
.entry_point = &js_math_desc,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* js_math_ep(void) {
|
||||
return &plugin_descriptor;
|
||||
}
|
||||
148
applications/system/js_app/modules/js_submenu.c
Normal file
148
applications/system/js_app/modules/js_submenu.c
Normal file
@@ -0,0 +1,148 @@
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/view_holder.h>
|
||||
#include <gui/view.h>
|
||||
#include <toolbox/api_lock.h>
|
||||
#include "../js_modules.h"
|
||||
|
||||
typedef struct {
|
||||
Submenu* submenu;
|
||||
ViewHolder* view_holder;
|
||||
FuriApiLock lock;
|
||||
uint32_t result;
|
||||
bool accepted;
|
||||
} JsSubmenuInst;
|
||||
|
||||
static JsSubmenuInst* get_this_ctx(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsSubmenuInst* submenu = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(submenu);
|
||||
return submenu;
|
||||
}
|
||||
|
||||
static void ret_bad_args(struct mjs* mjs, const char* error) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static bool check_arg_count(struct mjs* mjs, size_t count) {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args != count) {
|
||||
ret_bad_args(mjs, "Wrong argument count");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void submenu_callback(void* context, uint32_t id) {
|
||||
JsSubmenuInst* submenu = context;
|
||||
submenu->result = id;
|
||||
submenu->accepted = true;
|
||||
api_lock_unlock(submenu->lock);
|
||||
}
|
||||
|
||||
static void submenu_exit(void* context) {
|
||||
JsSubmenuInst* submenu = context;
|
||||
submenu->result = 0;
|
||||
submenu->accepted = false;
|
||||
api_lock_unlock(submenu->lock);
|
||||
}
|
||||
|
||||
static void js_submenu_add_item(struct mjs* mjs) {
|
||||
JsSubmenuInst* submenu = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 2)) return;
|
||||
|
||||
mjs_val_t label_arg = mjs_arg(mjs, 0);
|
||||
const char* label = mjs_get_string(mjs, &label_arg, NULL);
|
||||
if(!label) {
|
||||
ret_bad_args(mjs, "Label must be a string");
|
||||
return;
|
||||
}
|
||||
|
||||
mjs_val_t id_arg = mjs_arg(mjs, 1);
|
||||
if(!mjs_is_number(id_arg)) {
|
||||
ret_bad_args(mjs, "Id must be a number");
|
||||
return;
|
||||
}
|
||||
int32_t id = mjs_get_int32(mjs, id_arg);
|
||||
|
||||
submenu_add_item(submenu->submenu, label, id, submenu_callback, submenu);
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_submenu_set_header(struct mjs* mjs) {
|
||||
JsSubmenuInst* submenu = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 1)) return;
|
||||
|
||||
mjs_val_t header_arg = mjs_arg(mjs, 0);
|
||||
const char* header = mjs_get_string(mjs, &header_arg, NULL);
|
||||
if(!header) {
|
||||
ret_bad_args(mjs, "Header must be a string");
|
||||
return;
|
||||
}
|
||||
|
||||
submenu_set_header(submenu->submenu, header);
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_submenu_show(struct mjs* mjs) {
|
||||
JsSubmenuInst* submenu = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 0)) return;
|
||||
|
||||
submenu->lock = api_lock_alloc_locked();
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
submenu->view_holder = view_holder_alloc();
|
||||
view_holder_attach_to_gui(submenu->view_holder, gui);
|
||||
view_holder_set_back_callback(submenu->view_holder, submenu_exit, submenu);
|
||||
|
||||
view_holder_set_view(submenu->view_holder, submenu_get_view(submenu->submenu));
|
||||
view_holder_start(submenu->view_holder);
|
||||
api_lock_wait_unlock(submenu->lock);
|
||||
|
||||
view_holder_stop(submenu->view_holder);
|
||||
view_holder_free(submenu->view_holder);
|
||||
furi_record_close(RECORD_GUI);
|
||||
api_lock_free(submenu->lock);
|
||||
|
||||
submenu_reset(submenu->submenu);
|
||||
if(submenu->accepted) {
|
||||
mjs_return(mjs, mjs_mk_number(mjs, submenu->result));
|
||||
} else {
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
}
|
||||
|
||||
static void* js_submenu_create(struct mjs* mjs, mjs_val_t* object) {
|
||||
JsSubmenuInst* submenu = malloc(sizeof(JsSubmenuInst));
|
||||
mjs_val_t submenu_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, submenu_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, submenu));
|
||||
mjs_set(mjs, submenu_obj, "addItem", ~0, MJS_MK_FN(js_submenu_add_item));
|
||||
mjs_set(mjs, submenu_obj, "setHeader", ~0, MJS_MK_FN(js_submenu_set_header));
|
||||
mjs_set(mjs, submenu_obj, "show", ~0, MJS_MK_FN(js_submenu_show));
|
||||
submenu->submenu = submenu_alloc();
|
||||
*object = submenu_obj;
|
||||
return submenu;
|
||||
}
|
||||
|
||||
static void js_submenu_destroy(void* inst) {
|
||||
JsSubmenuInst* submenu = inst;
|
||||
submenu_free(submenu->submenu);
|
||||
free(submenu);
|
||||
}
|
||||
|
||||
static const JsModuleDescriptor js_submenu_desc = {
|
||||
"submenu",
|
||||
js_submenu_create,
|
||||
js_submenu_destroy,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor submenu_plugin_descriptor = {
|
||||
.appid = PLUGIN_APP_ID,
|
||||
.ep_api_version = PLUGIN_API_VERSION,
|
||||
.entry_point = &js_submenu_desc,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* js_submenu_ep(void) {
|
||||
return &submenu_plugin_descriptor;
|
||||
}
|
||||
220
applications/system/js_app/modules/js_textbox.c
Normal file
220
applications/system/js_app/modules/js_textbox.c
Normal file
@@ -0,0 +1,220 @@
|
||||
#include <gui/modules/text_box.h>
|
||||
#include <gui/view_holder.h>
|
||||
#include "../js_modules.h"
|
||||
|
||||
typedef struct {
|
||||
TextBox* text_box;
|
||||
ViewHolder* view_holder;
|
||||
FuriString* text;
|
||||
bool is_shown;
|
||||
} JsTextboxInst;
|
||||
|
||||
static JsTextboxInst* get_this_ctx(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsTextboxInst* textbox = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(textbox);
|
||||
return textbox;
|
||||
}
|
||||
|
||||
static void ret_bad_args(struct mjs* mjs, const char* error) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static bool check_arg_count(struct mjs* mjs, size_t count) {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args != count) {
|
||||
ret_bad_args(mjs, "Wrong argument count");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void js_textbox_set_config(struct mjs* mjs) {
|
||||
JsTextboxInst* textbox = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 2)) return;
|
||||
|
||||
TextBoxFocus set_focus = TextBoxFocusStart;
|
||||
mjs_val_t focus_arg = mjs_arg(mjs, 0);
|
||||
const char* focus = mjs_get_string(mjs, &focus_arg, NULL);
|
||||
if(!focus) {
|
||||
ret_bad_args(mjs, "Focus must be a string");
|
||||
return;
|
||||
} else {
|
||||
if(!strncmp(focus, "start", strlen("start"))) {
|
||||
set_focus = TextBoxFocusStart;
|
||||
} else if(!strncmp(focus, "end", strlen("end"))) {
|
||||
set_focus = TextBoxFocusEnd;
|
||||
} else {
|
||||
ret_bad_args(mjs, "Bad focus value");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TextBoxFont set_font = TextBoxFontText;
|
||||
mjs_val_t font_arg = mjs_arg(mjs, 1);
|
||||
const char* font = mjs_get_string(mjs, &font_arg, NULL);
|
||||
if(!font) {
|
||||
ret_bad_args(mjs, "Font must be a string");
|
||||
return;
|
||||
} else {
|
||||
if(!strncmp(font, "text", strlen("text"))) {
|
||||
set_font = TextBoxFontText;
|
||||
} else if(!strncmp(font, "hex", strlen("hex"))) {
|
||||
set_font = TextBoxFontHex;
|
||||
} else {
|
||||
ret_bad_args(mjs, "Bad font value");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
text_box_set_focus(textbox->text_box, set_focus);
|
||||
text_box_set_font(textbox->text_box, set_font);
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_textbox_add_text(struct mjs* mjs) {
|
||||
JsTextboxInst* textbox = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 1)) return;
|
||||
|
||||
mjs_val_t text_arg = mjs_arg(mjs, 0);
|
||||
size_t text_len = 0;
|
||||
const char* text = mjs_get_string(mjs, &text_arg, &text_len);
|
||||
if(!text) {
|
||||
ret_bad_args(mjs, "Text must be a string");
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid condition race between GUI and JS thread
|
||||
text_box_set_text(textbox->text_box, "");
|
||||
|
||||
size_t new_len = furi_string_size(textbox->text) + text_len;
|
||||
if(new_len >= 4096) {
|
||||
furi_string_right(textbox->text, new_len / 2);
|
||||
}
|
||||
|
||||
furi_string_cat(textbox->text, text);
|
||||
|
||||
text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text));
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_textbox_clear_text(struct mjs* mjs) {
|
||||
JsTextboxInst* textbox = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 0)) return;
|
||||
|
||||
// Avoid condition race between GUI and JS thread
|
||||
text_box_set_text(textbox->text_box, "");
|
||||
|
||||
furi_string_reset(textbox->text);
|
||||
|
||||
text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text));
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_textbox_is_open(struct mjs* mjs) {
|
||||
JsTextboxInst* textbox = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 0)) return;
|
||||
|
||||
mjs_return(mjs, mjs_mk_boolean(mjs, textbox->is_shown));
|
||||
}
|
||||
|
||||
static void textbox_callback(void* context, uint32_t arg) {
|
||||
UNUSED(arg);
|
||||
JsTextboxInst* textbox = context;
|
||||
view_holder_stop(textbox->view_holder);
|
||||
textbox->is_shown = false;
|
||||
}
|
||||
|
||||
static void textbox_exit(void* context) {
|
||||
JsTextboxInst* textbox = context;
|
||||
// Using timer to schedule view_holder stop, will not work under high CPU load
|
||||
furi_timer_pending_callback(textbox_callback, textbox, 0);
|
||||
}
|
||||
|
||||
static void js_textbox_show(struct mjs* mjs) {
|
||||
JsTextboxInst* textbox = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 0)) return;
|
||||
|
||||
if(textbox->is_shown) {
|
||||
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Textbox is already shown");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
view_holder_start(textbox->view_holder);
|
||||
textbox->is_shown = true;
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void js_textbox_close(struct mjs* mjs) {
|
||||
JsTextboxInst* textbox = get_this_ctx(mjs);
|
||||
if(!check_arg_count(mjs, 0)) return;
|
||||
|
||||
view_holder_stop(textbox->view_holder);
|
||||
textbox->is_shown = false;
|
||||
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
static void* js_textbox_create(struct mjs* mjs, mjs_val_t* object) {
|
||||
JsTextboxInst* textbox = malloc(sizeof(JsTextboxInst));
|
||||
|
||||
mjs_val_t textbox_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, textbox_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, textbox));
|
||||
mjs_set(mjs, textbox_obj, "setConfig", ~0, MJS_MK_FN(js_textbox_set_config));
|
||||
mjs_set(mjs, textbox_obj, "addText", ~0, MJS_MK_FN(js_textbox_add_text));
|
||||
mjs_set(mjs, textbox_obj, "clearText", ~0, MJS_MK_FN(js_textbox_clear_text));
|
||||
mjs_set(mjs, textbox_obj, "isOpen", ~0, MJS_MK_FN(js_textbox_is_open));
|
||||
mjs_set(mjs, textbox_obj, "show", ~0, MJS_MK_FN(js_textbox_show));
|
||||
mjs_set(mjs, textbox_obj, "close", ~0, MJS_MK_FN(js_textbox_close));
|
||||
|
||||
textbox->text = furi_string_alloc();
|
||||
textbox->text_box = text_box_alloc();
|
||||
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
textbox->view_holder = view_holder_alloc();
|
||||
view_holder_attach_to_gui(textbox->view_holder, gui);
|
||||
view_holder_set_back_callback(textbox->view_holder, textbox_exit, textbox);
|
||||
view_holder_set_view(textbox->view_holder, text_box_get_view(textbox->text_box));
|
||||
|
||||
*object = textbox_obj;
|
||||
return textbox;
|
||||
}
|
||||
|
||||
static void js_textbox_destroy(void* inst) {
|
||||
JsTextboxInst* textbox = inst;
|
||||
|
||||
view_holder_stop(textbox->view_holder);
|
||||
view_holder_free(textbox->view_holder);
|
||||
textbox->view_holder = NULL;
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
text_box_reset(textbox->text_box);
|
||||
furi_string_reset(textbox->text);
|
||||
|
||||
text_box_free(textbox->text_box);
|
||||
furi_string_free(textbox->text);
|
||||
free(textbox);
|
||||
}
|
||||
|
||||
static const JsModuleDescriptor js_textbox_desc = {
|
||||
"textbox",
|
||||
js_textbox_create,
|
||||
js_textbox_destroy,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor textbox_plugin_descriptor = {
|
||||
.appid = PLUGIN_APP_ID,
|
||||
.ep_api_version = PLUGIN_API_VERSION,
|
||||
.entry_point = &js_textbox_desc,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* js_textbox_ep(void) {
|
||||
return &textbox_plugin_descriptor;
|
||||
}
|
||||
Reference in New Issue
Block a user