mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2024-12-23 13:21:44 +03:00
Merge branch 'astra/3746-mfp-detect' of https://github.com/flipperdevices/flipperzero-firmware into astra/3746-mfp-detect
This commit is contained in:
commit
68eb196645
29
.github/workflows/build.yml
vendored
29
.github/workflows/build.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [f7, f18]
|
||||
target: [f7, f18]
|
||||
steps:
|
||||
- name: 'Wipe workspace'
|
||||
run: find ./ -mount -maxdepth 1 -exec rm -rf {} \;
|
||||
@ -103,29 +103,12 @@ jobs:
|
||||
run: |
|
||||
cp build/${DEFAULT_TARGET}-firmware-*/firmware.elf.map map_analyser_files/firmware.elf.map
|
||||
cp build/${DEFAULT_TARGET}-firmware-*/firmware.elf map_analyser_files/firmware.elf
|
||||
cp ${{ github.event_path }} map_analyser_files/event.json
|
||||
source scripts/toolchain/fbtenv.sh
|
||||
get_size()
|
||||
{
|
||||
SECTION="$1";
|
||||
arm-none-eabi-size \
|
||||
-A map_analyser_files/firmware.elf \
|
||||
| grep "^$SECTION" | awk '{print $2}'
|
||||
}
|
||||
export BSS_SIZE="$(get_size ".bss")"
|
||||
export TEXT_SIZE="$(get_size ".text")"
|
||||
export RODATA_SIZE="$(get_size ".rodata")"
|
||||
export DATA_SIZE="$(get_size ".data")"
|
||||
export FREE_FLASH_SIZE="$(get_size ".free_flash")"
|
||||
python3 -m pip install mariadb==1.1.6 cxxfilt==0.3.0
|
||||
python3 scripts/map_parser.py map_analyser_files/firmware.elf.map map_analyser_files/firmware.elf.map.all
|
||||
python3 scripts/map_mariadb_insert.py \
|
||||
${{ secrets.AMAP_MARIADB_USER }} \
|
||||
${{ secrets.AMAP_MARIADB_PASSWORD }} \
|
||||
${{ secrets.AMAP_MARIADB_HOST }} \
|
||||
${{ secrets.AMAP_MARIADB_PORT }} \
|
||||
${{ secrets.AMAP_MARIADB_DATABASE }} \
|
||||
map_analyser_files/firmware.elf.map.all
|
||||
python3 scripts/map_analyse_upload.py \
|
||||
"--elf_file=map_analyser_files/firmware.elf" \
|
||||
"--map_file=map_analyser_files/firmware.elf.map" \
|
||||
"--analyser_url=${{ secrets.ANALYSER_URL }}" \
|
||||
"--analyser_token=${{ secrets.ANALYSER_TOKEN }}";
|
||||
|
||||
- name: 'Find previous comment'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET && github.event.pull_request }}
|
||||
|
@ -54,6 +54,8 @@ jobs:
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY;
|
||||
echo "$MISSING_TICKETS" >> $GITHUB_STEP_SUMMARY;
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY;
|
||||
echo "Error: Missing issue number in comment(s):";
|
||||
echo "$MISSING_TICKETS";
|
||||
exit 1;
|
||||
else
|
||||
echo "No new TODOs without tickets found" >> $GITHUB_STEP_SUMMARY;
|
||||
|
@ -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;
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 5.1 KiB |
BIN
assets/icons/About/CertificationChina1_124x47.png
Normal file
BIN
assets/icons/About/CertificationChina1_124x47.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 429 B |
Binary file not shown.
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.6 KiB |
BIN
assets/icons/Settings/dolph_cry_49x54.png
Normal file
BIN
assets/icons/Settings/dolph_cry_49x54.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 973 B |
BIN
assets/icons/Settings/qr_benchmark_25x25.png
Normal file
BIN
assets/icons/Settings/qr_benchmark_25x25.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 395 B |
@ -243,7 +243,7 @@ size_t memmgr_heap_get_max_free_block(void) {
|
||||
|
||||
void memmgr_heap_printf_free_blocks(void) {
|
||||
BlockLink_t* pxBlock;
|
||||
//TODO enable when we can do printf with a locked scheduler
|
||||
//can be enabled once we can do printf with a locked scheduler
|
||||
//vTaskSuspendAll();
|
||||
|
||||
pxBlock = xStart.pxNextFreeBlock;
|
||||
|
@ -280,7 +280,7 @@ static void mjs_append_stack_trace_line(struct mjs* mjs, size_t offset) {
|
||||
const char* filename = mjs_get_bcode_filename_by_offset(mjs, offset);
|
||||
int line_no = mjs_get_lineno_by_offset(mjs, offset);
|
||||
char* new_line = NULL;
|
||||
const char* fmt = "at %s:%d\n";
|
||||
const char* fmt = "\tat %s:%d\r\n";
|
||||
if(filename == NULL) {
|
||||
// fprintf(
|
||||
// stderr,
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#define TAG "Iso15693_3Listener"
|
||||
|
||||
#define ISO15693_3_LISTENER_BUFFER_SIZE (64U)
|
||||
#define ISO15693_3_LISTENER_BUFFER_SIZE (256U)
|
||||
|
||||
Iso15693_3Listener* iso15693_3_listener_alloc(Nfc* nfc, Iso15693_3Data* data) {
|
||||
furi_assert(nfc);
|
||||
@ -67,6 +67,7 @@ NfcCommand iso15693_3_listener_run(NfcGenericEvent event, void* context) {
|
||||
if(nfc_event->type == NfcEventTypeRxEnd) {
|
||||
BitBuffer* rx_buffer = nfc_event->data.buffer;
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
if(iso13239_crc_check(Iso13239CrcTypeDefault, rx_buffer)) {
|
||||
iso13239_crc_trim(rx_buffer);
|
||||
|
||||
|
@ -64,7 +64,9 @@ static Iso15693_3Error iso15693_3_listener_inventory_handler(
|
||||
if(afi_flag) {
|
||||
const uint8_t afi = *data++;
|
||||
// When AFI flag is set, ignore non-matching requests
|
||||
if(afi != instance->data->system_info.afi) break;
|
||||
if(afi != 0) {
|
||||
if(afi != instance->data->system_info.afi) break;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t mask_len = *data++;
|
||||
@ -260,16 +262,9 @@ static Iso15693_3Error iso15693_3_listener_read_multi_blocks_handler(
|
||||
}
|
||||
|
||||
const uint32_t block_index_start = request->first_block_num;
|
||||
const uint32_t block_index_end = block_index_start + request->block_count;
|
||||
|
||||
const uint32_t block_count = request->block_count + 1;
|
||||
const uint32_t block_count_max = instance->data->system_info.block_count;
|
||||
const uint32_t block_count_available = block_count_max - block_index_start;
|
||||
|
||||
if(block_count > block_count_available) {
|
||||
error = Iso15693_3ErrorInternal;
|
||||
break;
|
||||
}
|
||||
const uint32_t block_index_end =
|
||||
MIN((block_index_start + request->block_count + 1),
|
||||
((uint32_t)instance->data->system_info.block_count - 1));
|
||||
|
||||
error = iso15693_3_listener_extension_handler(
|
||||
instance,
|
||||
|
@ -14,6 +14,7 @@
|
||||
#define SLIX_TYPE_INDICATOR_SLIX (0x02U)
|
||||
#define SLIX_TYPE_INDICATOR_SLIX2 (0x01U)
|
||||
|
||||
#define SLIX_CAPABILITIES_KEY "Capabilities"
|
||||
#define SLIX_PASSWORD_READ_KEY "Password Read"
|
||||
#define SLIX_PASSWORD_WRITE_KEY "Password Write"
|
||||
#define SLIX_PASSWORD_PRIVACY_KEY "Password Privacy"
|
||||
@ -69,6 +70,11 @@ static const SlixTypeFeatures slix_type_features[] = {
|
||||
[SlixTypeSlix2] = SLIX_TYPE_FEATURES_SLIX2,
|
||||
};
|
||||
|
||||
static const char* slix_capabilities_names[SlixCapabilitiesCount] = {
|
||||
[SlixCapabilitiesDefault] = "Default",
|
||||
[SlixCapabilitiesAcceptAllPasswords] = "AcceptAllPasswords",
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char* key;
|
||||
SlixTypeFeatures feature_flag;
|
||||
@ -110,6 +116,7 @@ void slix_reset(SlixData* data) {
|
||||
furi_check(data);
|
||||
|
||||
iso15693_3_reset(data->iso15693_3_data);
|
||||
data->capabilities = SlixCapabilitiesDefault;
|
||||
slix_password_set_defaults(data->passwords);
|
||||
|
||||
memset(&data->system_info, 0, sizeof(SlixSystemInfo));
|
||||
@ -123,6 +130,7 @@ void slix_copy(SlixData* data, const SlixData* other) {
|
||||
furi_check(other);
|
||||
|
||||
iso15693_3_copy(data->iso15693_3_data, other->iso15693_3_data);
|
||||
data->capabilities = other->capabilities;
|
||||
|
||||
memcpy(data->passwords, other->passwords, sizeof(SlixPassword) * SlixPasswordTypeCount);
|
||||
memcpy(data->signature, other->signature, sizeof(SlixSignature));
|
||||
@ -138,6 +146,30 @@ bool slix_verify(SlixData* data, const FuriString* device_type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool slix_load_capabilities(SlixData* data, FlipperFormat* ff) {
|
||||
bool capabilities_loaded = false;
|
||||
FuriString* capabilities_str = furi_string_alloc();
|
||||
|
||||
if(!flipper_format_read_string(ff, SLIX_CAPABILITIES_KEY, capabilities_str)) {
|
||||
if(flipper_format_rewind(ff)) {
|
||||
data->capabilities = SlixCapabilitiesDefault;
|
||||
capabilities_loaded = true;
|
||||
}
|
||||
} else {
|
||||
for(size_t i = 0; i < COUNT_OF(slix_capabilities_names); i++) {
|
||||
if(furi_string_cmp_str(capabilities_str, slix_capabilities_names[i]) == 0) {
|
||||
data->capabilities = i;
|
||||
capabilities_loaded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_free(capabilities_str);
|
||||
|
||||
return capabilities_loaded;
|
||||
}
|
||||
|
||||
static bool slix_load_passwords(SlixPassword* passwords, SlixType slix_type, FlipperFormat* ff) {
|
||||
bool ret = true;
|
||||
|
||||
@ -164,13 +196,14 @@ bool slix_load(SlixData* data, FlipperFormat* ff, uint32_t version) {
|
||||
furi_check(ff);
|
||||
|
||||
bool loaded = false;
|
||||
|
||||
do {
|
||||
if(!iso15693_3_load(data->iso15693_3_data, ff, version)) break;
|
||||
|
||||
const SlixType slix_type = slix_get_type(data);
|
||||
if(slix_type >= SlixTypeCount) break;
|
||||
|
||||
if(!slix_load_capabilities(data, ff)) break;
|
||||
|
||||
if(!slix_load_passwords(data->passwords, slix_type, ff)) break;
|
||||
|
||||
if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_SIGNATURE)) {
|
||||
@ -220,6 +253,33 @@ bool slix_load(SlixData* data, FlipperFormat* ff, uint32_t version) {
|
||||
return loaded;
|
||||
}
|
||||
|
||||
static bool slix_save_capabilities(const SlixData* data, FlipperFormat* ff) {
|
||||
bool save_success = false;
|
||||
|
||||
FuriString* tmp_str = furi_string_alloc();
|
||||
do {
|
||||
furi_string_set_str(
|
||||
tmp_str, "SLIX capabilities field affects emulation modes. Possible options: ");
|
||||
for(size_t i = 0; i < SlixCapabilitiesCount; i++) {
|
||||
furi_string_cat_str(tmp_str, slix_capabilities_names[i]);
|
||||
if(i < SlixCapabilitiesCount - 1) {
|
||||
furi_string_cat(tmp_str, ", ");
|
||||
}
|
||||
}
|
||||
if(!flipper_format_write_comment_cstr(ff, furi_string_get_cstr(tmp_str))) break;
|
||||
|
||||
if(!flipper_format_write_string_cstr(
|
||||
ff, SLIX_CAPABILITIES_KEY, slix_capabilities_names[data->capabilities]))
|
||||
break;
|
||||
|
||||
save_success = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(tmp_str);
|
||||
|
||||
return save_success;
|
||||
}
|
||||
|
||||
static bool
|
||||
slix_save_passwords(const SlixPassword* passwords, SlixType slix_type, FlipperFormat* ff) {
|
||||
bool ret = true;
|
||||
@ -251,6 +311,8 @@ bool slix_save(const SlixData* data, FlipperFormat* ff) {
|
||||
if(!iso15693_3_save(data->iso15693_3_data, ff)) break;
|
||||
if(!flipper_format_write_comment_cstr(ff, SLIX_PROTOCOL_NAME " specific data")) break;
|
||||
|
||||
if(!slix_save_capabilities(data, ff)) break;
|
||||
|
||||
if(!flipper_format_write_comment_cstr(
|
||||
ff,
|
||||
"Passwords are optional. If a password is omitted, a default value will be used"))
|
||||
|
@ -91,12 +91,20 @@ typedef struct {
|
||||
SlixLockBits lock_bits;
|
||||
} SlixSystemInfo;
|
||||
|
||||
typedef enum {
|
||||
SlixCapabilitiesDefault,
|
||||
SlixCapabilitiesAcceptAllPasswords,
|
||||
|
||||
SlixCapabilitiesCount,
|
||||
} SlixCapabilities;
|
||||
|
||||
typedef struct {
|
||||
Iso15693_3Data* iso15693_3_data;
|
||||
SlixSystemInfo system_info;
|
||||
SlixSignature signature;
|
||||
SlixPassword passwords[SlixPasswordTypeCount];
|
||||
SlixPrivacy privacy;
|
||||
SlixCapabilities capabilities;
|
||||
} SlixData;
|
||||
|
||||
SlixData* slix_alloc(void);
|
||||
|
@ -54,6 +54,13 @@ static SlixError slix_listener_set_password(
|
||||
}
|
||||
|
||||
SlixListenerSessionState* session_state = &instance->session_state;
|
||||
|
||||
// With AcceptAllPassword capability set skip password validation
|
||||
if(instance->data->capabilities == SlixCapabilitiesAcceptAllPasswords) {
|
||||
session_state->password_match[password_type] = true;
|
||||
break;
|
||||
}
|
||||
|
||||
session_state->password_match[password_type] =
|
||||
(password == slix_get_password(slix_data, password_type));
|
||||
|
||||
|
@ -114,7 +114,8 @@ static NfcCommand slix_poller_handler_check_privacy_password(SlixPoller* instanc
|
||||
break;
|
||||
}
|
||||
|
||||
instance->error = slix_poller_set_password(instance, SlixPasswordTypePrivacy, pwd);
|
||||
instance->error = slix_poller_set_password(
|
||||
instance, SlixPasswordTypePrivacy, pwd, instance->random_number);
|
||||
if(instance->error != SlixErrorNone) {
|
||||
command = NfcCommandReset;
|
||||
break;
|
||||
@ -145,7 +146,8 @@ static NfcCommand slix_poller_handler_privacy_unlock(SlixPoller* instance) {
|
||||
instance->error = slix_poller_get_random_number(instance, &instance->random_number);
|
||||
if(instance->error != SlixErrorNone) break;
|
||||
|
||||
instance->error = slix_poller_set_password(instance, SlixPasswordTypePrivacy, pwd);
|
||||
instance->error = slix_poller_set_password(
|
||||
instance, SlixPasswordTypePrivacy, pwd, instance->random_number);
|
||||
if(instance->error != SlixErrorNone) {
|
||||
command = NfcCommandReset;
|
||||
break;
|
||||
|
@ -107,12 +107,16 @@ SlixError slix_poller_get_random_number(SlixPoller* instance, SlixRandomNumber*
|
||||
* Must ONLY be used inside the callback function.
|
||||
*
|
||||
* @param[in, out] instance pointer to the instance to be used in the transaction.
|
||||
* @param[out] type SlixPasswordType instance.
|
||||
* @param[out] password SlixPassword instance.
|
||||
* @param[in] type SlixPasswordType instance.
|
||||
* @param[in] password SlixPassword instance.
|
||||
* @param[in] random_number SlixRandomNumber instance.
|
||||
* @return SlixErrorNone on success, an error code on failure.
|
||||
*/
|
||||
SlixError
|
||||
slix_poller_set_password(SlixPoller* instance, SlixPasswordType type, SlixPassword password);
|
||||
SlixError slix_poller_set_password(
|
||||
SlixPoller* instance,
|
||||
SlixPasswordType type,
|
||||
SlixPassword password,
|
||||
SlixRandomNumber random_number);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@ -92,8 +92,11 @@ SlixError slix_poller_get_random_number(SlixPoller* instance, SlixRandomNumber*
|
||||
return error;
|
||||
}
|
||||
|
||||
SlixError
|
||||
slix_poller_set_password(SlixPoller* instance, SlixPasswordType type, SlixPassword password) {
|
||||
SlixError slix_poller_set_password(
|
||||
SlixPoller* instance,
|
||||
SlixPasswordType type,
|
||||
SlixPassword password,
|
||||
SlixRandomNumber random_number) {
|
||||
furi_assert(instance);
|
||||
|
||||
bool skip_uid = (type == SlixPasswordTypePrivacy);
|
||||
@ -102,8 +105,8 @@ SlixError
|
||||
uint8_t password_type = (0x01 << type);
|
||||
bit_buffer_append_byte(instance->tx_buffer, password_type);
|
||||
|
||||
uint8_t rn_l = instance->random_number >> 8;
|
||||
uint8_t rn_h = instance->random_number;
|
||||
uint8_t rn_l = random_number >> 8;
|
||||
uint8_t rn_h = random_number;
|
||||
uint32_t double_rand_num = (rn_h << 24) | (rn_l << 16) | (rn_h << 8) | rn_l;
|
||||
uint32_t xored_password = double_rand_num ^ password;
|
||||
uint8_t xored_password_arr[4] = {};
|
||||
|
86
scripts/map_analyse_upload.py
Executable file
86
scripts/map_analyse_upload.py
Executable file
@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import requests
|
||||
import argparse
|
||||
import subprocess
|
||||
|
||||
# usage:
|
||||
# COMMIT_HASH, COMMIT_MSG, BRANCH_NAME,
|
||||
# PULL_ID(optional), PULL_NAME(optional) must be set as envs
|
||||
# maybe from sctipts/get_env.py
|
||||
# other args must be set via command line args
|
||||
|
||||
|
||||
class AnalyseRequest:
|
||||
def __init__(self):
|
||||
self.commit_hash = os.environ["COMMIT_HASH"]
|
||||
self.commit_msg = os.environ["COMMIT_MSG"]
|
||||
self.branch_name = os.environ["BRANCH_NAME"]
|
||||
self.pull_id = os.getenv("PULL_ID", default=None)
|
||||
self.pull_name = os.getenv("PULL_NAME", default=None)
|
||||
|
||||
def get_payload(self):
|
||||
return vars(self)
|
||||
|
||||
|
||||
class AnalyseUploader:
|
||||
def __init__(self):
|
||||
self.args = self.parse_args()
|
||||
|
||||
@staticmethod
|
||||
def get_sections_size(elf_file) -> dict:
|
||||
ret = dict()
|
||||
all_sizes = subprocess.check_output(
|
||||
["arm-none-eabi-size", "-A", elf_file], shell=False
|
||||
)
|
||||
all_sizes = all_sizes.splitlines()
|
||||
|
||||
sections_to_keep = (".text", ".rodata", ".data", ".bss", ".free_flash")
|
||||
for line in all_sizes:
|
||||
line = line.decode("utf-8")
|
||||
parts = line.split()
|
||||
if len(parts) != 3:
|
||||
continue
|
||||
section, size, _ = parts
|
||||
if section not in sections_to_keep:
|
||||
continue
|
||||
section_size_payload_name = (
|
||||
section[1:] if section.startswith(".") else section
|
||||
)
|
||||
section_size_payload_name += "_size"
|
||||
ret[section_size_payload_name] = size
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--elf_file", help="Firmware ELF file", required=True)
|
||||
parser.add_argument("--map_file", help="Firmware MAP file", required=True)
|
||||
parser.add_argument(
|
||||
"--analyser_token", help="Analyser auth token", required=True
|
||||
)
|
||||
parser.add_argument(
|
||||
"--analyser_url", help="Analyser analyse url", required=True
|
||||
)
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
def upload_analyse_request(self):
|
||||
payload = AnalyseRequest().get_payload() | self.get_sections_size(
|
||||
self.args.elf_file
|
||||
)
|
||||
headers = {"Authorization": f"Bearer {self.args.analyser_token}"}
|
||||
file = {"map_file": open(self.args.map_file, "rb")}
|
||||
response = requests.post(
|
||||
self.args.analyser_url, data=payload, files=file, headers=headers
|
||||
)
|
||||
if not response.ok:
|
||||
raise Exception(
|
||||
f"Failed to upload map file, code: {response.status_code}, reason: {response.text}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
analyzer = AnalyseUploader()
|
||||
analyzer.upload_analyse_request()
|
@ -1,139 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Requiremets:
|
||||
# mariadb==1.1.6
|
||||
|
||||
from datetime import datetime
|
||||
import argparse
|
||||
import mariadb
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
def parseArgs():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("db_user", help="MariaDB user")
|
||||
parser.add_argument("db_pass", help="MariaDB password")
|
||||
parser.add_argument("db_host", help="MariaDB hostname")
|
||||
parser.add_argument("db_port", type=int, help="MariaDB port")
|
||||
parser.add_argument("db_name", help="MariaDB database")
|
||||
parser.add_argument("report_file", help="Report file(.map.all)")
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
def mariadbConnect(args):
|
||||
try:
|
||||
conn = mariadb.connect(
|
||||
user=args.db_user,
|
||||
password=args.db_pass,
|
||||
host=args.db_host,
|
||||
port=args.db_port,
|
||||
database=args.db_name,
|
||||
)
|
||||
except mariadb.Error as e:
|
||||
print(f"Error connecting to MariaDB: {e}")
|
||||
sys.exit(1)
|
||||
return conn
|
||||
|
||||
|
||||
def parseEnv():
|
||||
outArr = []
|
||||
outArr.append(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||
outArr.append(os.getenv("COMMIT_HASH", default=None))
|
||||
outArr.append(os.getenv("COMMIT_MSG", default=None))
|
||||
outArr.append(os.getenv("BRANCH_NAME", default=None))
|
||||
outArr.append(os.getenv("BSS_SIZE", default=None))
|
||||
outArr.append(os.getenv("TEXT_SIZE", default=None))
|
||||
outArr.append(os.getenv("RODATA_SIZE", default=None))
|
||||
outArr.append(os.getenv("DATA_SIZE", default=None))
|
||||
outArr.append(os.getenv("FREE_FLASH_SIZE", default=None))
|
||||
outArr.append(os.getenv("PULL_ID", default=None))
|
||||
outArr.append(os.getenv("PULL_NAME", default=None))
|
||||
return outArr
|
||||
|
||||
|
||||
def createTables(cur, conn):
|
||||
headerTable = "CREATE TABLE IF NOT EXISTS `header` ( \
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, \
|
||||
`datetime` datetime NOT NULL, \
|
||||
`commit` varchar(40) NOT NULL, \
|
||||
`commit_msg` text NOT NULL, \
|
||||
`branch_name` text NOT NULL, \
|
||||
`bss_size` int(10) unsigned NOT NULL, \
|
||||
`text_size` int(10) unsigned NOT NULL, \
|
||||
`rodata_size` int(10) unsigned NOT NULL, \
|
||||
`data_size` int(10) unsigned NOT NULL, \
|
||||
`free_flash_size` int(10) unsigned NOT NULL, \
|
||||
`pullrequest_id` int(10) unsigned DEFAULT NULL, \
|
||||
`pullrequest_name` text DEFAULT NULL, \
|
||||
PRIMARY KEY (`id`), \
|
||||
KEY `header_id_index` (`id`) )"
|
||||
dataTable = "CREATE TABLE IF NOT EXISTS `data` ( \
|
||||
`header_id` int(10) unsigned NOT NULL, \
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, \
|
||||
`section` text NOT NULL, \
|
||||
`address` text NOT NULL, \
|
||||
`size` int(10) unsigned NOT NULL, \
|
||||
`name` text NOT NULL, \
|
||||
`lib` text NOT NULL, \
|
||||
`obj_name` text NOT NULL, \
|
||||
PRIMARY KEY (`id`), \
|
||||
KEY `data_id_index` (`id`), \
|
||||
KEY `data_header_id_index` (`header_id`), \
|
||||
CONSTRAINT `data_header_id_foreign` FOREIGN KEY (`header_id`) REFERENCES `header` (`id`) )"
|
||||
cur.execute(headerTable)
|
||||
cur.execute(dataTable)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def insertHeader(data, cur, conn):
|
||||
query = "INSERT INTO `header` ( \
|
||||
datetime, commit, commit_msg, branch_name, bss_size, text_size, \
|
||||
rodata_size, data_size, free_flash_size, pullrequest_id, pullrequest_name) \
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
cur.execute(query, data)
|
||||
conn.commit()
|
||||
return cur.lastrowid
|
||||
|
||||
|
||||
def parseFile(fileObj, headerID):
|
||||
arr = []
|
||||
fileLines = fileObj.readlines()
|
||||
for line in fileLines:
|
||||
lineArr = []
|
||||
tempLineArr = line.split("\t")
|
||||
lineArr.append(headerID)
|
||||
lineArr.append(tempLineArr[0]) # section
|
||||
lineArr.append(int(tempLineArr[2], 16)) # address hex
|
||||
lineArr.append(int(tempLineArr[3])) # size
|
||||
lineArr.append(tempLineArr[4]) # name
|
||||
lineArr.append(tempLineArr[5]) # lib
|
||||
lineArr.append(tempLineArr[6]) # obj_name
|
||||
arr.append(tuple(lineArr))
|
||||
return arr
|
||||
|
||||
|
||||
def insertData(data, cur, conn):
|
||||
query = "INSERT INTO `data` ( \
|
||||
header_id, section, address, size, \
|
||||
name, lib, obj_name) \
|
||||
VALUES (?, ?, ?, ?, ? ,?, ?)"
|
||||
cur.executemany(query, data)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def main():
|
||||
args = parseArgs()
|
||||
dbConn = mariadbConnect(args)
|
||||
reportFile = open(args.report_file)
|
||||
dbCurs = dbConn.cursor()
|
||||
createTables(dbCurs, dbConn)
|
||||
headerID = insertHeader(parseEnv(), dbCurs, dbConn)
|
||||
insertData(parseFile(reportFile, headerID), dbCurs, dbConn)
|
||||
reportFile.close()
|
||||
dbCurs.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,274 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Requiremets:
|
||||
# cxxfilt==0.3.0
|
||||
|
||||
# Most part of this code written by Lars-Dominik Braun <lars@6xq.net> https://github.com/PromyLOPh/linkermapviz
|
||||
# and distributes under MIT licence
|
||||
|
||||
# Copyright (c) 2017 Lars-Dominik Braun <lars@6xq.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
from typing import TextIO
|
||||
from cxxfilt import demangle
|
||||
|
||||
|
||||
class Objectfile:
|
||||
def __init__(self, section: str, offset: int, size: int, comment: str):
|
||||
self.section = section.strip()
|
||||
self.offset = offset
|
||||
self.size = size
|
||||
self.path = (None, None)
|
||||
self.basepath = None
|
||||
|
||||
if comment:
|
||||
self.path = re.match(r"^(.+?)(?:\(([^\)]+)\))?$", comment).groups()
|
||||
self.basepath = os.path.basename(self.path[0])
|
||||
|
||||
self.children = []
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Objectfile {self.section} {self.offset:x} {self.size:x} {self.path} {repr(self.children)}>"
|
||||
|
||||
|
||||
def update_children_size(children: list[list], subsection_size: int) -> list:
|
||||
# set subsection size to an only child
|
||||
if len(children) == 1:
|
||||
children[0][1] = subsection_size
|
||||
return children
|
||||
|
||||
rest_size = subsection_size
|
||||
|
||||
for index in range(1, len(children)):
|
||||
if rest_size > 0:
|
||||
# current size = current address - previous child address
|
||||
child_size = children[index][0] - children[index - 1][0]
|
||||
rest_size -= child_size
|
||||
children[index - 1][1] = child_size
|
||||
|
||||
# if there is rest size, set it to the last child element
|
||||
if rest_size > 0:
|
||||
children[-1][1] = rest_size
|
||||
|
||||
return children
|
||||
|
||||
|
||||
def parse_sections(file_name: str) -> list:
|
||||
"""
|
||||
Quick&Dirty parsing for GNU ld’s linker map output, needs LANG=C, because
|
||||
some messages are localized.
|
||||
"""
|
||||
|
||||
sections = []
|
||||
with open(file_name, "r") as file:
|
||||
# skip until memory map is found
|
||||
found = False
|
||||
|
||||
while True:
|
||||
line = file.readline()
|
||||
if not line:
|
||||
break
|
||||
if line.strip() == "Memory Configuration":
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
raise Exception(f"Memory configuration is not found in the{input_file}")
|
||||
|
||||
# long section names result in a linebreak afterwards
|
||||
sectionre = re.compile(
|
||||
"(?P<section>.+?|.{14,}\n)[ ]+0x(?P<offset>[0-9a-f]+)[ ]+0x(?P<size>[0-9a-f]+)(?:[ ]+(?P<comment>.+))?\n+",
|
||||
re.I,
|
||||
)
|
||||
subsectionre = re.compile(
|
||||
"[ ]{16}0x(?P<offset>[0-9a-f]+)[ ]+(?P<function>.+)\n+", re.I
|
||||
)
|
||||
s = file.read()
|
||||
pos = 0
|
||||
|
||||
while True:
|
||||
m = sectionre.match(s, pos)
|
||||
if not m:
|
||||
# skip that line
|
||||
try:
|
||||
nextpos = s.index("\n", pos) + 1
|
||||
pos = nextpos
|
||||
continue
|
||||
except ValueError:
|
||||
break
|
||||
|
||||
pos = m.end()
|
||||
section = m.group("section")
|
||||
v = m.group("offset")
|
||||
offset = int(v, 16) if v is not None else None
|
||||
v = m.group("size")
|
||||
size = int(v, 16) if v is not None else None
|
||||
comment = m.group("comment")
|
||||
|
||||
if section != "*default*" and size > 0:
|
||||
of = Objectfile(section, offset, size, comment)
|
||||
|
||||
if section.startswith(" "):
|
||||
children = []
|
||||
sections[-1].children.append(of)
|
||||
|
||||
while True:
|
||||
m = subsectionre.match(s, pos)
|
||||
if not m:
|
||||
break
|
||||
pos = m.end()
|
||||
offset, function = m.groups()
|
||||
offset = int(offset, 16)
|
||||
if sections and sections[-1].children:
|
||||
children.append([offset, 0, function])
|
||||
|
||||
if children:
|
||||
children = update_children_size(
|
||||
children=children, subsection_size=of.size
|
||||
)
|
||||
|
||||
sections[-1].children[-1].children.extend(children)
|
||||
|
||||
else:
|
||||
sections.append(of)
|
||||
|
||||
return sections
|
||||
|
||||
|
||||
def get_subsection_name(section_name: str, subsection: Objectfile) -> str:
|
||||
subsection_split_names = subsection.section.split(".")
|
||||
if subsection.section.startswith("."):
|
||||
subsection_split_names = subsection_split_names[1:]
|
||||
|
||||
return (
|
||||
f".{subsection_split_names[1]}"
|
||||
if len(subsection_split_names) > 2
|
||||
else section_name
|
||||
)
|
||||
|
||||
|
||||
def write_subsection(
|
||||
section_name: str,
|
||||
subsection_name: str,
|
||||
address: str,
|
||||
size: int,
|
||||
demangled_name: str,
|
||||
module_name: str,
|
||||
file_name: str,
|
||||
mangled_name: str,
|
||||
write_file_object: TextIO,
|
||||
) -> None:
|
||||
write_file_object.write(
|
||||
f"{section_name}\t"
|
||||
f"{subsection_name}\t"
|
||||
f"{address}\t"
|
||||
f"{size}\t"
|
||||
f"{demangled_name}\t"
|
||||
f"{module_name}\t"
|
||||
f"{file_name}\t"
|
||||
f"{mangled_name}\n"
|
||||
)
|
||||
|
||||
|
||||
def save_subsection(
|
||||
section_name: str, subsection: Objectfile, write_file_object: TextIO
|
||||
) -> None:
|
||||
subsection_name = get_subsection_name(section_name, subsection)
|
||||
module_name = subsection.path[0]
|
||||
file_name = subsection.path[1]
|
||||
|
||||
if not file_name:
|
||||
file_name, module_name = module_name, ""
|
||||
|
||||
if not subsection.children:
|
||||
address = f"{subsection.offset:x}"
|
||||
size = subsection.size
|
||||
mangled_name = (
|
||||
""
|
||||
if subsection.section == section_name
|
||||
else subsection.section.split(".")[-1]
|
||||
)
|
||||
demangled_name = demangle(mangled_name) if mangled_name else mangled_name
|
||||
|
||||
write_subsection(
|
||||
section_name=section_name,
|
||||
subsection_name=subsection_name,
|
||||
address=address,
|
||||
size=size,
|
||||
demangled_name=demangled_name,
|
||||
module_name=module_name,
|
||||
file_name=file_name,
|
||||
mangled_name=mangled_name,
|
||||
write_file_object=write_file_object,
|
||||
)
|
||||
return
|
||||
|
||||
for subsection_child in subsection.children:
|
||||
address = f"{subsection_child[0]:x}"
|
||||
size = subsection_child[1]
|
||||
mangled_name = subsection_child[2]
|
||||
demangled_name = demangle(mangled_name)
|
||||
|
||||
write_subsection(
|
||||
section_name=section_name,
|
||||
subsection_name=subsection_name,
|
||||
address=address,
|
||||
size=size,
|
||||
demangled_name=demangled_name,
|
||||
module_name=module_name,
|
||||
file_name=file_name,
|
||||
mangled_name=mangled_name,
|
||||
write_file_object=write_file_object,
|
||||
)
|
||||
|
||||
|
||||
def save_section(section: Objectfile, write_file_object: TextIO) -> None:
|
||||
section_name = section.section
|
||||
for subsection in section.children:
|
||||
save_subsection(
|
||||
section_name=section_name,
|
||||
subsection=subsection,
|
||||
write_file_object=write_file_object,
|
||||
)
|
||||
|
||||
|
||||
def save_parsed_data(parsed_data: list[Objectfile], output_file_name: str) -> None:
|
||||
with open(output_file_name, "w") as write_file_object:
|
||||
for section in parsed_data:
|
||||
if section.children:
|
||||
save_section(section=section, write_file_object=write_file_object)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 3:
|
||||
raise Exception(f"Usage: {sys.argv[0]} <input file> <output file>")
|
||||
|
||||
input_file = sys.argv[1]
|
||||
output_file = sys.argv[2]
|
||||
|
||||
parsed_sections = parse_sections(input_file)
|
||||
|
||||
if parsed_sections is None:
|
||||
raise Exception(f"Memory configuration is not {input_file}")
|
||||
|
||||
save_parsed_data(parsed_sections, output_file)
|
@ -1,5 +1,5 @@
|
||||
entry,status,name,type,params
|
||||
Version,+,61.2,,
|
||||
Version,+,61.4,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
Header,+,applications/services/cli/cli.h,,
|
||||
Header,+,applications/services/cli/cli_vcp.h,,
|
||||
@ -27,6 +27,7 @@ Header,+,applications/services/gui/modules/variable_item_list.h,,
|
||||
Header,+,applications/services/gui/modules/widget.h,,
|
||||
Header,+,applications/services/gui/modules/widget_elements/widget_element.h,,
|
||||
Header,+,applications/services/gui/view_dispatcher.h,,
|
||||
Header,+,applications/services/gui/view_holder.h,,
|
||||
Header,+,applications/services/gui/view_stack.h,,
|
||||
Header,+,applications/services/input/input.h,,
|
||||
Header,+,applications/services/loader/firmware_api/firmware_api.h,,
|
||||
@ -2554,7 +2555,9 @@ Function,-,strxfrm,size_t,"char*, const char*, size_t"
|
||||
Function,-,strxfrm_l,size_t,"char*, const char*, size_t, locale_t"
|
||||
Function,+,submenu_add_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*"
|
||||
Function,+,submenu_alloc,Submenu*,
|
||||
Function,+,submenu_change_item_label,void,"Submenu*, uint32_t, const char*"
|
||||
Function,+,submenu_free,void,Submenu*
|
||||
Function,+,submenu_get_selected_item,uint32_t,Submenu*
|
||||
Function,+,submenu_get_view,View*,Submenu*
|
||||
Function,+,submenu_reset,void,Submenu*
|
||||
Function,+,submenu_set_header,void,"Submenu*, const char*"
|
||||
@ -2681,6 +2684,16 @@ Function,+,view_dispatcher_switch_to_view,void,"ViewDispatcher*, uint32_t"
|
||||
Function,+,view_free,void,View*
|
||||
Function,+,view_free_model,void,View*
|
||||
Function,+,view_get_model,void*,View*
|
||||
Function,+,view_holder_alloc,ViewHolder*,
|
||||
Function,+,view_holder_attach_to_gui,void,"ViewHolder*, Gui*"
|
||||
Function,+,view_holder_free,void,ViewHolder*
|
||||
Function,+,view_holder_get_free_context,void*,ViewHolder*
|
||||
Function,+,view_holder_set_back_callback,void,"ViewHolder*, BackCallback, void*"
|
||||
Function,+,view_holder_set_free_callback,void,"ViewHolder*, FreeCallback, void*"
|
||||
Function,+,view_holder_set_view,void,"ViewHolder*, View*"
|
||||
Function,+,view_holder_start,void,ViewHolder*
|
||||
Function,+,view_holder_stop,void,ViewHolder*
|
||||
Function,+,view_holder_update,void,"View*, void*"
|
||||
Function,+,view_port_alloc,ViewPort*,
|
||||
Function,+,view_port_draw_callback_set,void,"ViewPort*, ViewPortDrawCallback, void*"
|
||||
Function,+,view_port_enabled_set,void,"ViewPort*, _Bool"
|
||||
|
|
@ -1,5 +1,5 @@
|
||||
entry,status,name,type,params
|
||||
Version,+,61.2,,
|
||||
Version,+,61.4,,
|
||||
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
Header,+,applications/services/cli/cli.h,,
|
||||
@ -28,6 +28,7 @@ Header,+,applications/services/gui/modules/variable_item_list.h,,
|
||||
Header,+,applications/services/gui/modules/widget.h,,
|
||||
Header,+,applications/services/gui/modules/widget_elements/widget_element.h,,
|
||||
Header,+,applications/services/gui/view_dispatcher.h,,
|
||||
Header,+,applications/services/gui/view_holder.h,,
|
||||
Header,+,applications/services/gui/view_stack.h,,
|
||||
Header,+,applications/services/input/input.h,,
|
||||
Header,+,applications/services/loader/firmware_api/firmware_api.h,,
|
||||
@ -3366,7 +3367,9 @@ Function,+,subghz_worker_start,void,SubGhzWorker*
|
||||
Function,+,subghz_worker_stop,void,SubGhzWorker*
|
||||
Function,+,submenu_add_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*"
|
||||
Function,+,submenu_alloc,Submenu*,
|
||||
Function,+,submenu_change_item_label,void,"Submenu*, uint32_t, const char*"
|
||||
Function,+,submenu_free,void,Submenu*
|
||||
Function,+,submenu_get_selected_item,uint32_t,Submenu*
|
||||
Function,+,submenu_get_view,View*,Submenu*
|
||||
Function,+,submenu_reset,void,Submenu*
|
||||
Function,+,submenu_set_header,void,"Submenu*, const char*"
|
||||
@ -3496,6 +3499,16 @@ Function,+,view_dispatcher_switch_to_view,void,"ViewDispatcher*, uint32_t"
|
||||
Function,+,view_free,void,View*
|
||||
Function,+,view_free_model,void,View*
|
||||
Function,+,view_get_model,void*,View*
|
||||
Function,+,view_holder_alloc,ViewHolder*,
|
||||
Function,+,view_holder_attach_to_gui,void,"ViewHolder*, Gui*"
|
||||
Function,+,view_holder_free,void,ViewHolder*
|
||||
Function,+,view_holder_get_free_context,void*,ViewHolder*
|
||||
Function,+,view_holder_set_back_callback,void,"ViewHolder*, BackCallback, void*"
|
||||
Function,+,view_holder_set_free_callback,void,"ViewHolder*, FreeCallback, void*"
|
||||
Function,+,view_holder_set_view,void,"ViewHolder*, View*"
|
||||
Function,+,view_holder_start,void,ViewHolder*
|
||||
Function,+,view_holder_stop,void,ViewHolder*
|
||||
Function,+,view_holder_update,void,"View*, void*"
|
||||
Function,+,view_port_alloc,ViewPort*,
|
||||
Function,+,view_port_draw_callback_set,void,"ViewPort*, ViewPortDrawCallback, void*"
|
||||
Function,+,view_port_enabled_set,void,"ViewPort*, _Bool"
|
||||
|
|
Loading…
Reference in New Issue
Block a user