From 2961ad5ee1aa184752abd58d1cd85260755dc3fd Mon Sep 17 00:00:00 2001 From: gornekich Date: Thu, 7 Mar 2024 15:59:27 +0000 Subject: [PATCH 01/30] slix: fix incorrect default privacy passwords --- lib/nfc/protocols/slix/slix.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/nfc/protocols/slix/slix.c b/lib/nfc/protocols/slix/slix.c index b575baa72..6361787d3 100644 --- a/lib/nfc/protocols/slix/slix.c +++ b/lib/nfc/protocols/slix/slix.c @@ -78,8 +78,8 @@ typedef struct { static const SlixPasswordConfig slix_password_configs[] = { [SlixPasswordTypeRead] = {SLIX_PASSWORD_READ_KEY, SLIX_TYPE_FEATURE_READ, 0x00000000U}, [SlixPasswordTypeWrite] = {SLIX_PASSWORD_WRITE_KEY, SLIX_TYPE_FEATURE_WRITE, 0x00000000U}, - [SlixPasswordTypePrivacy] = {SLIX_PASSWORD_PRIVACY_KEY, SLIX_TYPE_FEATURE_PRIVACY, 0xFFFFFFFFU}, - [SlixPasswordTypeDestroy] = {SLIX_PASSWORD_DESTROY_KEY, SLIX_TYPE_FEATURE_DESTROY, 0xFFFFFFFFU}, + [SlixPasswordTypePrivacy] = {SLIX_PASSWORD_PRIVACY_KEY, SLIX_TYPE_FEATURE_PRIVACY, 0x0F0F0F0FU}, + [SlixPasswordTypeDestroy] = {SLIX_PASSWORD_DESTROY_KEY, SLIX_TYPE_FEATURE_DESTROY, 0x0F0F0F0FU}, [SlixPasswordTypeEasAfi] = {SLIX_PASSWORD_EAS_KEY, SLIX_TYPE_FEATURE_EAS, 0x00000000U}, }; From bf97209c9b5859a498579a3736956315d905b26e Mon Sep 17 00:00:00 2001 From: gornekich Date: Thu, 7 Mar 2024 17:31:53 +0000 Subject: [PATCH 02/30] slix poller: add check privacy password state --- lib/nfc/protocols/slix/slix_poller.c | 51 +++++++++++++++++++++++++- lib/nfc/protocols/slix/slix_poller_i.h | 2 + 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/lib/nfc/protocols/slix/slix_poller.c b/lib/nfc/protocols/slix/slix_poller.c index d9d38d102..3c9a7cce4 100644 --- a/lib/nfc/protocols/slix/slix_poller.c +++ b/lib/nfc/protocols/slix/slix_poller.c @@ -73,17 +73,62 @@ static NfcCommand slix_poller_handler_read_signature(SlixPoller* instance) { if(slix_type_has_features(instance->type, SLIX_TYPE_FEATURE_SIGNATURE)) { instance->error = slix_poller_read_signature(instance, &instance->data->signature); if(instance->error == SlixErrorNone) { - instance->poller_state = SlixPollerStateReady; + instance->poller_state = SlixPollerStateCheckPrivacyPassword; } else { instance->poller_state = SlixPollerStateError; } } else { - instance->poller_state = SlixPollerStateReady; + instance->poller_state = SlixPollerStateCheckPrivacyPassword; } return NfcCommandContinue; } +static NfcCommand slix_poller_handler_check_privacy_password(SlixPoller* instance) { + NfcCommand command = NfcCommandContinue; + + do { + if(!slix_type_has_features(instance->type, SLIX_TYPE_FEATURE_PRIVACY)) { + instance->poller_state = SlixPollerStateReady; + break; + } + if(instance->privacy_password_checked) { + instance->poller_state = SlixPollerStateReady; + break; + } + + instance->slix_event.type = SlixPollerEventTypePrivacyUnlockRequest; + command = instance->callback(instance->general_event, instance->context); + + if(!instance->slix_event_data.privacy_password.password_set) { + instance->poller_state = SlixPollerStateReady; + break; + } + + SlixPassword pwd = instance->slix_event_data.privacy_password.password; + FURI_LOG_I(TAG, "Trying to check privacy password: %08lX", pwd); + + instance->error = slix_poller_get_random_number(instance, &instance->random_number); + if(instance->error != SlixErrorNone) { + instance->poller_state = SlixPollerStateReady; + break; + } + + instance->error = slix_poller_set_password(instance, SlixPasswordTypePrivacy, pwd); + if(instance->error != SlixErrorNone) { + command = NfcCommandReset; + break; + } + + FURI_LOG_I(TAG, "Found privacy password"); + instance->data->passwords[SlixPasswordTypePrivacy] = pwd; + instance->privacy_password_checked = true; + instance->poller_state = SlixPollerStateReady; + } while(false); + + return command; +} + static NfcCommand slix_poller_handler_privacy_unlock(SlixPoller* instance) { NfcCommand command = NfcCommandContinue; instance->poller_state = SlixPollerStateError; @@ -108,6 +153,7 @@ static NfcCommand slix_poller_handler_privacy_unlock(SlixPoller* instance) { FURI_LOG_I(TAG, "Privacy mode disabled"); instance->data->passwords[SlixPasswordTypePrivacy] = pwd; + instance->privacy_password_checked = true; instance->poller_state = SlixPollerStateIdle; slix_unlocked = true; } while(false); @@ -140,6 +186,7 @@ static const SlixPollerStateHandler slix_poller_state_handler[SlixPollerStateNum [SlixPollerStateError] = slix_poller_handler_error, [SlixPollerStateGetNxpSysInfo] = slix_poller_handler_get_nfc_system_info, [SlixPollerStateReadSignature] = slix_poller_handler_read_signature, + [SlixPollerStateCheckPrivacyPassword] = slix_poller_handler_check_privacy_password, [SlixPollerStatePrivacyUnlock] = slix_poller_handler_privacy_unlock, [SlixPollerStateReady] = slix_poller_handler_ready, }; diff --git a/lib/nfc/protocols/slix/slix_poller_i.h b/lib/nfc/protocols/slix/slix_poller_i.h index 7a3b543b7..38c23c297 100644 --- a/lib/nfc/protocols/slix/slix_poller_i.h +++ b/lib/nfc/protocols/slix/slix_poller_i.h @@ -14,6 +14,7 @@ typedef enum { SlixPollerStateIdle, SlixPollerStateGetNxpSysInfo, SlixPollerStateReadSignature, + SlixPollerStateCheckPrivacyPassword, SlixPollerStatePrivacyUnlock, SlixPollerStateReady, SlixPollerStateError, @@ -27,6 +28,7 @@ struct SlixPoller { SlixPollerState poller_state; SlixError error; SlixRandomNumber random_number; + bool privacy_password_checked; BitBuffer* tx_buffer; BitBuffer* rx_buffer; From 8f9fbc3a79c9266cb22074d3f8fb3f06409ed46c Mon Sep 17 00:00:00 2001 From: gornekich Date: Thu, 7 Mar 2024 18:48:14 +0000 Subject: [PATCH 03/30] mf desfire: change detect algorithm --- lib/nfc/protocols/mf_desfire/mf_desfire_poller.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c index 5af033d4c..864ca09a4 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c @@ -225,8 +225,8 @@ static bool mf_desfire_poller_detect(NfcGenericEvent event, void* context) { bool protocol_detected = false; if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { - MfDesfireVersion version = {}; - const MfDesfireError error = mf_desfire_poller_read_version(instance, &version); + MfDesfireKeySettings key_settings = {}; + MfDesfireError error = mf_desfire_poller_read_key_settings(instance, &key_settings); protocol_detected = (error == MfDesfireErrorNone); } From b1a9904ea5a2ef6c70022ad979360406bc07c7e3 Mon Sep 17 00:00:00 2001 From: gornekich Date: Thu, 7 Mar 2024 19:29:18 +0000 Subject: [PATCH 04/30] mf desfire: change detection algorithm to check master key version --- .../protocols/mf_desfire/mf_desfire_poller.c | 4 +-- .../protocols/mf_desfire/mf_desfire_poller.h | 15 ++++++++ .../mf_desfire/mf_desfire_poller_i.c | 35 ++++++++++++------- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 3 +- 5 files changed, 43 insertions(+), 16 deletions(-) diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c index 864ca09a4..f6e638d7c 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c @@ -225,8 +225,8 @@ static bool mf_desfire_poller_detect(NfcGenericEvent event, void* context) { bool protocol_detected = false; if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { - MfDesfireKeySettings key_settings = {}; - MfDesfireError error = mf_desfire_poller_read_key_settings(instance, &key_settings); + MfDesfireKeyVersion key_version = 0; + MfDesfireError error = mf_desfire_poller_read_key_version(instance, 0, &key_version); protocol_detected = (error == MfDesfireErrorNone); } diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h index 6ef2f3f68..707df42cd 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h @@ -91,6 +91,21 @@ MfDesfireError MfDesfireError mf_desfire_poller_read_key_settings(MfDesfirePoller* instance, MfDesfireKeySettings* data); +/** + * @brief Read key version on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] key_num key number. + * @param[in] data pointer to the MfDesfireKeyVersion structure to be filled with key version data. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_read_key_version( + MfDesfirePoller* instance, + uint8_t key_num, + MfDesfireKeyVersion* data); + /** * @brief Read key versions on MfDesfire card. * diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index 0b2d84138..1a91c5261 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -139,6 +139,28 @@ MfDesfireError return error; } +MfDesfireError mf_desfire_poller_read_key_version( + MfDesfirePoller* instance, + uint8_t key_num, + MfDesfireKeyVersion* data) { + furi_assert(instance); + furi_assert(data); + + bit_buffer_set_size_bytes(instance->input_buffer, sizeof(uint8_t) * 2); + bit_buffer_set_byte(instance->input_buffer, 0, MF_DESFIRE_CMD_GET_KEY_VERSION); + bit_buffer_set_byte(instance->input_buffer, 1, key_num); + + MfDesfireError error = + mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + if(error == MfDesfireErrorNone) { + if(!mf_desfire_key_version_parse(data, instance->result_buffer)) { + error = MfDesfireErrorProtocol; + } + } + + return error; +} + MfDesfireError mf_desfire_poller_read_key_versions( MfDesfirePoller* instance, SimpleArray* data, @@ -148,22 +170,11 @@ MfDesfireError mf_desfire_poller_read_key_versions( simple_array_init(data, count); - bit_buffer_set_size_bytes(instance->input_buffer, sizeof(uint8_t) * 2); - bit_buffer_set_byte(instance->input_buffer, 0, MF_DESFIRE_CMD_GET_KEY_VERSION); - MfDesfireError error = MfDesfireErrorNone; for(uint32_t i = 0; i < count; ++i) { - bit_buffer_set_byte(instance->input_buffer, 1, i); - - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); - + error = mf_desfire_poller_read_key_version(instance, i, simple_array_get(data, i)); if(error != MfDesfireErrorNone) break; - - if(!mf_desfire_key_version_parse(simple_array_get(data, i), instance->result_buffer)) { - error = MfDesfireErrorProtocol; - break; - } } return error; diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index bdfa8c7a4..8f6b8530e 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,58.0,, +Version,+,58.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index d856dc694..b0a3d1610 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,58.0,, +Version,+,58.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2444,6 +2444,7 @@ Function,+,mf_desfire_poller_read_file_settings_multi,MfDesfireError,"MfDesfireP Function,+,mf_desfire_poller_read_file_value,MfDesfireError,"MfDesfirePoller*, MfDesfireFileId, MfDesfireFileData*" Function,+,mf_desfire_poller_read_free_memory,MfDesfireError,"MfDesfirePoller*, MfDesfireFreeMemory*" Function,+,mf_desfire_poller_read_key_settings,MfDesfireError,"MfDesfirePoller*, MfDesfireKeySettings*" +Function,+,mf_desfire_poller_read_key_version,MfDesfireError,"MfDesfirePoller*, uint8_t, MfDesfireKeyVersion*" Function,+,mf_desfire_poller_read_key_versions,MfDesfireError,"MfDesfirePoller*, SimpleArray*, uint32_t" Function,+,mf_desfire_poller_read_version,MfDesfireError,"MfDesfirePoller*, MfDesfireVersion*" Function,+,mf_desfire_poller_select_application,MfDesfireError,"MfDesfirePoller*, const MfDesfireApplicationId*" From 79a286fe1a445d39d0a03952811db62c06c66811 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 11 Mar 2024 03:53:32 +0300 Subject: [PATCH 05/30] upd changelog --- CHANGELOG.md | 110 ++++++++++++++++++-------------- documentation/SubGHzSettings.md | 1 + 2 files changed, 62 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6dab2eaa..ebde2dc4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,58 +1,70 @@ ## New changes -* NFC: **Fixed stuck Saved success screen** -* NFC: **Fixed crash when reading mifare classic tag then going to add manually menu and adding NFC-A tag** -* NFC: Fixed EMV txs render -* NFC/LFRFID: Don't Stop emulation after 5 mins to avoid antenna damage if debug is ON (by @Leptopt1los) -* LFRFID: Fixed T5577 custom password input (by @Leptopt1los) -* OFW PR 3410: lfrfid/em4100: added support for different bit rates - by @Mrkvak (RF/32 full support, RF/16 support without reading (16clk removed for now)) -* OFW PR 3412: Fixed MyKey LockID - by @zProAle -
----
-**Changes from 070 release:**
-* NFC: **EMV parser** added (by @Leptopt1los and @wosk | PR #700) -* NFC: Metromoney parser balance fix (by @Leptopt1los | PR #699) -* NFC/LFRFID: Stop emulation after 5 mins to avoid antenna damage (by @Leptopt1los) -* Archive: Fix two filebrowser bugs -* SubGHz: **Programming mode for Dea Mio** (right arrow button) -* SubGHz: **Keeloq fix emulation for multiple systems and extend add manually support** for 2 of them (Dea Mio, Genius Bravo, GSN, Normstahl) -* SubGHz: Fixed hopper state when entering Read via Freq analyzer -* SubGHz: Raw erase fixes (by @Willy-JL) -* SubGHz: Subghz save files with receive time (by @Willy-JL) -* NFC: Fix NFC V dumps with v3 (pre refactor saves) crashing at info page -* NFC: Zolotaya Korona Online parser added (by @Leptopt1los) -* NFC: Add NFC **NDEF parser** (by @Willy-JL) -* LF RFID: **Write T5577 with random and custom password** added (clear password via Extra actions) (by @Leptopt1los) -* SubGHz: Update honeywell protocol (by @Willy-JL) -* System: More contrast values for replacement displays (up to +8 or -8) -* USB/BLE HID: Add macOS Music app volume control +* NFC: EMV Fixes and imporvements (by @wosk & @Leptopt1los | PR #702) +* NFC: Parsers refactoring (by @Leptopt1los) +* NFC: Kazan parser improved - token parse option added (by @Leptopt1los) +* NFC: Update ndef parser, mf classic dict changes (by @Willy-JL) +* Infrared: Update universal remote assets (by @amec0e | PR #718 #719) +* SubGHz: Add 430.50 mhz (by @MizumasuShoichi | PR #721) +* SubGHz: Magellan Event Code Update (by @wooferguy | PR #713) +* SubGHz: Reduce subghz add manually scene flash size (by @Willy-JL) +* SubGHz: Fix led blink on decode raw > signal info (by @Willy-JL) +* HID App: apply fix for ms teams on macos (by @cpressland) +* HID App: merge official fw hid app keyboard changes +* Expansion `is_connected` API to check for VGM (by @HaxSam) +* New JavaScript Modules `UsbDisk`,`badusb.quit()`,`SubGHz`,`Submenu`,`BleBeacon`,`Keyboard`,`Math` (by @Willy-JL, @Spooks4576, @Sil333033) * Apps: **Check out Apps updates by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) -* OFW PR 3384: NFC: Display unread Mifare Classic bytes as question marks - by @TollyH -* OFW PR 3396: NFC: **fix application opening from browser** - by @RebornedBrain (+ fix for leftover issues) -* OFW PR 3382: NFC UI refactor - by @RebornedBrain -* OFW PR 3391: Rework more info scene for Ultralight cards - by @RebornedBrain -* OFW PR 3401: it-IT-mac layout - by @nminaylov -* OFW: Fix expansion protocol crash when fed lots of garbage -* OFW: 0.98.0-rc various fixes -* OFW: RFID CLI: better usage -* OFW: **Mf DESFire fixes** -* OFW: NFC UI refactor -* OFW: **Expansion module protocol** (+ expansion settings read and store in ram by @Willy-JL) -* OFW: Bugfix: Strip last parity bit from decoded FDX-B data -* OFW: FuriHal: interrupt priorities and documentation -* OFW: FuriHal: **UART refactoring** -* OFW: SubGhz: add `subghz tx_from_file` CLI cmd, major TX flow refactoring, various improvements and bug fixes -* OFW: Furi_hal_rtc: new function +* OFW: Fix troika 4K keys +* OFW: Archive: Fix item focus after aborting the Delete operation +* OFW: Troyka parser improvements (by UL Team) +* OFW: NFC: Fix washcity plugin verify function being to greedy +* OFW: Parser for Santiago, Chile BIP transit card +* OFW: WiFi board: fixed update script on Windows (unfortunately also Mac and Linux) +* OFW: Gui: reset canvas orientation and frame when entering direct draw mode +* OFW: FBT/uFBT: Enable C++20/GNU23 in VSCode IntelliSense +* OFW: Toolchain fixes +* OFW: Quote $FBT_TOOLCHAIN_PATH to avoid splitting +* OFW: ble: profile rework +* OFW: lfrfid/em4100: added support for different bit rates (16clk was added back into UL, still not reading properly) +* OFW: T5577 lib: write with mask function added +* OFW: Archive: fixed Apps tab ext filter +* OFW: FuriHalRtc refactor: new datetime lib (by UL Team) +* OFW: bit_lib and nfc_util refactor (by UL Team) +* OFW: Gui text box: fix formatted string memory reservation +* OFW: JS debug disabled, archive and file browser fixes +* OFW: VSCode integration fixes for new toolchain +* OFW: FIX ISO15693 emulation +* OFW: JS serial module renamed, uart channel selection +* OFW: mjs: minor fixes +* OFW: **JavaScript runner** +* OFW: Fixed MyKey check LockID +* OFW: Check universal remote files before loading +* OFW: NFC: fix retry scene navigation logic +* OFW: Expansion module service improvements +* OFW: New toolchain with gcc 12 (+ aarch64 support!) +* OFW: HID app: keyboard modifiers fix +* OFW: CLI: cat command crash workaround +* OFW: NFC: Custom UID entry when adding manually +* OFW: Added NFC plugin; Some parser +* OFW: **Slix disable privacy** (Unlock SLIX-L) +* OFW: NFC: Add support for Gallagher access control (MIFARE Classic only) +* OFW: furi/core/timer: resolve timer handle use-after-free post deletion +* OFW: FuriHal: various GPIO improvements +* OFW: GUI: canvas commit callback has been moved to canvas. Direct Draw apps can now be streamed via RPC. +* OFW: nfc app: fix incorrect protocol detection in save scene (by UL Team) +* OFW: NFC: MFC Unlock with Dictionary +* OFW: ITSO Parser (UK) +* OFW: NFC: fix application opening from browser +* OFW: Rework more info scene for Ultralight cards * OFW: NFC UI refactor -* OFW: assets: checking limits on image size; ufbt: cdb target -* OFW: NFC: system dict skip when user dict is skipped fix (replaces our fix) -* OFW: FuriHal: fix start duration furi_hal_subghz_async_tx -* OFW: NFC: parsers minor cleanup -* OFW: NFC Ntag success write freeze when not saved card -* OFW: ufbt: fixed generated project paths on Windows +* OFW: Add an NFC parser for the San Francisco Bay Area "Clipper" transit card. +* OFW: Fix nfc_protocol_support_scene_save_name_on_event crash +* OFW: NFC: Display unread Mifare Classic bytes as question marks +* OFW: Troika layout fixes +* OFW: NFC: MF Classic parsers read() fix (dictionary attack skip)

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) -- Option to unlock Slix-L (NFC V) with preset or custom password was removed with refactoring (OFW) -- NFC CLI was removed with refactoring (OFW) +- NFC CLI was removed with refactoring (OFW) (will be back soon) - Current list of affected apps: https://github.com/xMasterX/all-the-plugins/tree/dev/apps_broken_by_last_refactors - Also in app **Enhanced Sub-GHz Chat** - NFC part was temporarily removed to make app usable, NFC part of the app requires remaking it with new nfc stack diff --git a/documentation/SubGHzSettings.md b/documentation/SubGHzSettings.md index 1988dcbd8..3c952a1a3 100644 --- a/documentation/SubGHzSettings.md +++ b/documentation/SubGHzSettings.md @@ -40,6 +40,7 @@ if you need your custom one, make sure it doesn't listed here 390000000, 418000000, 430000000, + 430500000, 431000000, 431500000, 433075000, /* LPD433 first */ From adbe4d44f4c8019d31740909216998c6b848b286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 11 Mar 2024 22:09:39 +0900 Subject: [PATCH 06/30] DateTime: fix missing weekday in datetime_timestamp_to_datetime conversion (#3508) --- .../unit_tests/datetimelib/datetimelib_test.c | 20 +++++++++++++++++-- lib/datetime/datetime.c | 1 + 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/applications/debug/unit_tests/datetimelib/datetimelib_test.c b/applications/debug/unit_tests/datetimelib/datetimelib_test.c index 42bc7dbed..bf8e6fabd 100644 --- a/applications/debug/unit_tests/datetimelib/datetimelib_test.c +++ b/applications/debug/unit_tests/datetimelib/datetimelib_test.c @@ -112,7 +112,7 @@ MU_TEST_SUITE(test_datetime_validate_datetime) { MU_TEST(test_datetime_timestamp_to_datetime_min) { uint32_t test_value = 0; - DateTime min_datetime_expected = {0, 0, 0, 1, 1, 1970, 0}; + DateTime min_datetime_expected = {0, 0, 0, 1, 1, 1970, 4}; DateTime result = {0}; datetime_timestamp_to_datetime(test_value, &result); @@ -122,7 +122,7 @@ MU_TEST(test_datetime_timestamp_to_datetime_min) { MU_TEST(test_datetime_timestamp_to_datetime_max) { uint32_t test_value = UINT32_MAX; - DateTime max_datetime_expected = {6, 28, 15, 7, 2, 2106, 0}; + DateTime max_datetime_expected = {6, 28, 15, 7, 2, 2106, 7}; DateTime result = {0}; datetime_timestamp_to_datetime(test_value, &result); @@ -141,10 +141,26 @@ MU_TEST(test_datetime_timestamp_to_datetime_to_timestamp) { mu_assert_int_eq(test_value, result); } +MU_TEST(test_datetime_timestamp_to_datetime_weekday) { + uint32_t test_value = 1709748421; // Wed Mar 06 18:07:01 2024 UTC + + DateTime datetime = {0}; + datetime_timestamp_to_datetime(test_value, &datetime); + + mu_assert_int_eq(datetime.hour, 18); + mu_assert_int_eq(datetime.minute, 7); + mu_assert_int_eq(datetime.second, 1); + mu_assert_int_eq(datetime.day, 6); + mu_assert_int_eq(datetime.month, 3); + mu_assert_int_eq(datetime.weekday, 3); + mu_assert_int_eq(datetime.year, 2024); +} + MU_TEST_SUITE(test_datetime_timestamp_to_datetime_suite) { MU_RUN_TEST(test_datetime_timestamp_to_datetime_min); MU_RUN_TEST(test_datetime_timestamp_to_datetime_max); MU_RUN_TEST(test_datetime_timestamp_to_datetime_to_timestamp); + MU_RUN_TEST(test_datetime_timestamp_to_datetime_weekday); } MU_TEST(test_datetime_datetime_to_timestamp_min) { diff --git a/lib/datetime/datetime.c b/lib/datetime/datetime.c index 7b1f73dd0..73044fae6 100644 --- a/lib/datetime/datetime.c +++ b/lib/datetime/datetime.c @@ -71,6 +71,7 @@ void datetime_timestamp_to_datetime(uint32_t timestamp, DateTime* datetime) { uint32_t seconds_in_day = timestamp % SECONDS_PER_DAY; datetime->year = EPOCH_START_YEAR; + datetime->weekday = ((days + 3) % 7) + 1; while(days >= datetime_get_days_per_year(datetime->year)) { days -= datetime_get_days_per_year(datetime->year); From 022fccf0d79bb2c3b68e5d55d12f67c073ad7f32 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Mon, 11 Mar 2024 20:35:51 +0300 Subject: [PATCH 07/30] [FL-3783] Asynchronous Infrared remote manipulation (#3503) * Introduce ConcurrentRunner, load universal and regular remotes concurrently * Perform all lengthy operations in a ConcurrentRunner * Fix python formatting * Clean up code * Add usage warning * Remove ConcurrentRunner, use a plain FuriThread instead * Load remotes asynchronously in RPC mode as well * Reorder code for clarity * Clean up, use thread return code to report errors * Improve wording * Fix logical error --- applications/main/infrared/infrared_app.c | 36 ++++---- applications/main/infrared/infrared_app_i.h | 39 +++++--- .../main/infrared/infrared_custom_event.h | 1 + .../common/infrared_scene_universal_common.c | 35 ++++++-- .../scenes/infrared_scene_edit_delete.c | 60 ++++++++----- .../scenes/infrared_scene_edit_move.c | 38 ++++---- .../scenes/infrared_scene_edit_rename.c | 58 +++++++----- .../scenes/infrared_scene_remote_list.c | 60 ++++++++----- .../main/infrared/scenes/infrared_scene_rpc.c | 90 ++++++++++++------- .../scenes/infrared_scene_universal_ac.c | 13 +-- .../scenes/infrared_scene_universal_audio.c | 13 +-- .../infrared_scene_universal_projector.c | 13 +-- .../scenes/infrared_scene_universal_tv.c | 13 +-- 13 files changed, 269 insertions(+), 200 deletions(-) diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index 645659bbc..50534c660 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -6,7 +6,8 @@ #define TAG "InfraredApp" -#define INFRARED_TX_MIN_INTERVAL_MS 50U +#define INFRARED_TX_MIN_INTERVAL_MS (50U) +#define INFRARED_TASK_STACK_SIZE (2048UL) static const NotificationSequence* infrared_notification_sequences[InfraredNotificationMessageCount] = { @@ -128,6 +129,8 @@ static void infrared_find_vacant_remote_name(FuriString* name, const char* path) static InfraredApp* infrared_alloc() { InfraredApp* infrared = malloc(sizeof(InfraredApp)); + infrared->task_thread = + furi_thread_alloc_ex("InfraredTask", INFRARED_TASK_STACK_SIZE, NULL, infrared); infrared->file_path = furi_string_alloc(); infrared->button_name = furi_string_alloc(); @@ -203,6 +206,10 @@ static InfraredApp* infrared_alloc() { static void infrared_free(InfraredApp* infrared) { furi_assert(infrared); + + furi_thread_join(infrared->task_thread); + furi_thread_free(infrared->task_thread); + ViewDispatcher* view_dispatcher = infrared->view_dispatcher; InfraredAppState* app_state = &infrared->app_state; @@ -377,6 +384,18 @@ void infrared_tx_stop(InfraredApp* infrared) { infrared->app_state.last_transmit_time = furi_get_tick(); } +void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback) { + view_stack_add_view(infrared->view_stack, loading_get_view(infrared->loading)); + furi_thread_set_callback(infrared->task_thread, callback); + furi_thread_start(infrared->task_thread); +} + +bool infrared_blocking_task_finalize(InfraredApp* infrared) { + furi_thread_join(infrared->task_thread); + view_stack_remove_view(infrared->view_stack, loading_get_view(infrared->loading)); + return furi_thread_get_return_code(infrared->task_thread); +} + void infrared_text_store_set(InfraredApp* infrared, uint32_t bank, const char* fmt, ...) { va_list args; va_start(args, fmt); @@ -397,21 +416,6 @@ void infrared_play_notification_message( notification_message(infrared->notifications, infrared_notification_sequences[message]); } -void infrared_show_loading_popup(const InfraredApp* infrared, bool show) { - ViewStack* view_stack = infrared->view_stack; - Loading* loading = infrared->loading; - - if(show) { - // Raise timer priority so that animations can play - furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated); - view_stack_add_view(view_stack, loading_get_view(loading)); - } else { - view_stack_remove_view(view_stack, loading_get_view(loading)); - // Restore default timer priority - furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal); - } -} - void infrared_show_error_message(const InfraredApp* infrared, const char* fmt, ...) { va_list args; va_start(args, fmt); diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index 7a9202b28..bccd58608 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -19,12 +19,13 @@ #include #include +#include #include #include #include -#include +#include #include "infrared_app.h" #include "infrared_remote.h" @@ -36,8 +37,6 @@ #include "views/infrared_debug_view.h" #include "views/infrared_move_view.h" -#include "rpc/rpc_app.h" - #define INFRARED_FILE_NAME_SIZE 100 #define INFRARED_TEXT_STORE_NUM 2 #define INFRARED_TEXT_STORE_SIZE 128 @@ -120,6 +119,7 @@ struct InfraredApp { Loading* loading; /**< Standard view for informing about long operations. */ InfraredProgressView* progress; /**< Custom view for showing brute force progress. */ + FuriThread* task_thread; /**< Pointer to a FuriThread instance for concurrent tasks. */ FuriString* file_path; /**< Full path to the currently loaded file. */ FuriString* button_name; /**< Name of the button requested in RPC mode. */ /** Arbitrary text storage for various inputs. */ @@ -210,6 +210,28 @@ void infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index); */ void infrared_tx_stop(InfraredApp* infrared); +/** + * @brief Start a blocking task in a separate thread. + * + * If a ViewStack is currently on screen, a busy "Hourglass" animation + * will be shown and no input will be accepted until completion. + * + * @param[in,out] infrared pointer to the application instance. + * @param[in] callback pointer to the function to be run in the thread. + */ +void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback); + +/** + * @brief Wait for a blocking task to finish and receive the result. + * + * Upon the completion of a blocking task, the busy animation will be hidden + * and input will be accepted again. + * + * @param[in,out] infrared pointer to the application instance. + * @return true if the blocking task finished successfully, false otherwise. + */ +bool infrared_blocking_task_finalize(InfraredApp* infrared); + /** * @brief Set the internal text store with formatted text. * @@ -239,17 +261,6 @@ void infrared_play_notification_message( const InfraredApp* infrared, InfraredNotificationMessage message); -/** - * @brief Show a loading pop-up screen. - * - * In order for this to work, a Stack view must be currently active and - * the main view must be added to it. - * - * @param[in] infrared pointer to the application instance. - * @param[in] show whether to show or hide the pop-up. - */ -void infrared_show_loading_popup(const InfraredApp* infrared, bool show); - /** * @brief Show a formatted error messsage. * diff --git a/applications/main/infrared/infrared_custom_event.h b/applications/main/infrared/infrared_custom_event.h index 30bb0f729..b53e52a2f 100644 --- a/applications/main/infrared/infrared_custom_event.h +++ b/applications/main/infrared/infrared_custom_event.h @@ -14,6 +14,7 @@ enum InfraredCustomEventType { InfraredCustomEventTypePopupClosed, InfraredCustomEventTypeButtonSelected, InfraredCustomEventTypeBackPressed, + InfraredCustomEventTypeTaskFinished, InfraredCustomEventTypeRpcLoadFile, InfraredCustomEventTypeRpcExit, diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c index 4967d1956..9fc48bd46 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c @@ -32,9 +32,24 @@ static void infrared_scene_universal_common_hide_popup(InfraredApp* infrared) { infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop); } +static int32_t infrared_scene_universal_common_task_callback(void* context) { + InfraredApp* infrared = context; + const bool success = infrared_brute_force_calculate_messages(infrared->brute_force); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, + infrared_custom_event_pack(InfraredCustomEventTypeTaskFinished, 0)); + + return success; +} + void infrared_scene_universal_common_on_enter(void* context) { InfraredApp* infrared = context; + view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); view_stack_add_view(infrared->view_stack, button_panel_get_view(infrared->button_panel)); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); + + // Load universal remote data in background + infrared_blocking_task_start(infrared, infrared_scene_universal_common_task_callback); } bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) { @@ -58,26 +73,34 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e if(infrared_custom_event_get_type(event.event) == InfraredCustomEventTypeBackPressed) { infrared_brute_force_stop(brute_force); infrared_scene_universal_common_hide_popup(infrared); - consumed = true; } + consumed = true; } } else { if(event.type == SceneManagerEventTypeBack) { scene_manager_previous_scene(scene_manager); consumed = true; } else if(event.type == SceneManagerEventTypeCustom) { - if(infrared_custom_event_get_type(event.event) == - InfraredCustomEventTypeButtonSelected) { + uint16_t event_type; + int16_t event_value; + infrared_custom_event_unpack(event.event, &event_type, &event_value); + + if(event_type == InfraredCustomEventTypeButtonSelected) { uint32_t record_count; - if(infrared_brute_force_start( - brute_force, infrared_custom_event_get_value(event.event), &record_count)) { + if(infrared_brute_force_start(brute_force, event_value, &record_count)) { dolphin_deed(DolphinDeedIrSend); infrared_scene_universal_common_show_popup(infrared, record_count); } else { scene_manager_next_scene(scene_manager, InfraredSceneErrorDatabases); } - consumed = true; + } else if(event_type == InfraredCustomEventTypeTaskFinished) { + const bool task_success = infrared_blocking_task_finalize(infrared); + + if(!task_success) { + scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); + } } + consumed = true; } } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_delete.c b/applications/main/infrared/scenes/infrared_scene_edit_delete.c index 0cb88efdb..8dc4ab6f9 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_delete.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_delete.c @@ -6,12 +6,33 @@ static void view_dispatcher_send_custom_event(infrared->view_dispatcher, result); } +static int32_t infrared_scene_edit_delete_task_callback(void* context) { + InfraredApp* infrared = context; + InfraredAppState* app_state = &infrared->app_state; + const InfraredEditTarget edit_target = app_state->edit_target; + + bool success; + if(edit_target == InfraredEditTargetButton) { + furi_assert(app_state->current_button_index != InfraredButtonIndexNone); + success = infrared_remote_delete_signal(infrared->remote, app_state->current_button_index); + } else if(edit_target == InfraredEditTargetRemote) { + success = infrared_remote_remove(infrared->remote); + } else { + furi_crash(); + } + + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); + + return success; +} + void infrared_scene_edit_delete_on_enter(void* context) { InfraredApp* infrared = context; DialogEx* dialog_ex = infrared->dialog_ex; InfraredRemote* remote = infrared->remote; - const InfraredEditTarget edit_target = infrared->app_state.edit_target; + if(edit_target == InfraredEditTargetButton) { dialog_ex_set_header(dialog_ex, "Delete Button?", 64, 0, AlignCenter, AlignTop); @@ -84,39 +105,30 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeCustom) { if(event.event == DialogExResultLeft) { scene_manager_previous_scene(scene_manager); - consumed = true; } else if(event.event == DialogExResultRight) { - bool success = false; - InfraredRemote* remote = infrared->remote; + // Delete a button or a remote in a separate thread + infrared_blocking_task_start(infrared, infrared_scene_edit_delete_task_callback); + + } else if(event.event == InfraredCustomEventTypeTaskFinished) { + const bool task_success = infrared_blocking_task_finalize(infrared); + InfraredAppState* app_state = &infrared->app_state; - const InfraredEditTarget edit_target = app_state->edit_target; - - if(edit_target == InfraredEditTargetButton) { - furi_assert(app_state->current_button_index != InfraredButtonIndexNone); - infrared_show_loading_popup(infrared, true); - success = infrared_remote_delete_signal(remote, app_state->current_button_index); - infrared_show_loading_popup(infrared, false); - app_state->current_button_index = InfraredButtonIndexNone; - } else if(edit_target == InfraredEditTargetRemote) { - success = infrared_remote_remove(remote); - app_state->current_button_index = InfraredButtonIndexNone; - } else { - furi_crash(); - } - if(success) { + if(task_success) { scene_manager_next_scene(scene_manager, InfraredSceneEditDeleteDone); } else { - infrared_show_error_message( - infrared, - "Failed to\ndelete %s", - edit_target == InfraredEditTargetButton ? "button" : "file"); + const char* edit_target_text = + app_state->edit_target == InfraredEditTargetButton ? "button" : "file"; + infrared_show_error_message(infrared, "Failed to\ndelete %s", edit_target_text); + const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart}; scene_manager_search_and_switch_to_previous_scene_one_of( scene_manager, possible_scenes, COUNT_OF(possible_scenes)); } - consumed = true; + + app_state->current_button_index = InfraredButtonIndexNone; } + consumed = true; } return consumed; diff --git a/applications/main/infrared/scenes/infrared_scene_edit_move.c b/applications/main/infrared/scenes/infrared_scene_edit_move.c index 4959a8310..500f3d791 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_move.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_move.c @@ -1,5 +1,17 @@ #include "../infrared_app_i.h" +static int32_t infrared_scene_edit_move_task_callback(void* context) { + InfraredApp* infrared = context; + const bool success = infrared_remote_move_signal( + infrared->remote, + infrared->app_state.prev_button_index, + infrared->app_state.current_button_index); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); + + return success; +} + static void infrared_scene_edit_move_button_callback( uint32_t index_old, uint32_t index_new, @@ -38,25 +50,21 @@ bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == InfraredCustomEventTypeButtonSelected) { - infrared_show_loading_popup(infrared, true); - const bool button_moved = infrared_remote_move_signal( - infrared->remote, - infrared->app_state.prev_button_index, - infrared->app_state.current_button_index); - infrared_show_loading_popup(infrared, false); - - if(!button_moved) { - infrared_show_error_message( - infrared, - "Failed to move\n\"%s\"", - infrared_remote_get_signal_name( - infrared->remote, infrared->app_state.current_button_index)); + // Move the button in a separate thread + infrared_blocking_task_start(infrared, infrared_scene_edit_move_task_callback); + + } else if(event.event == InfraredCustomEventTypeTaskFinished) { + const bool task_success = infrared_blocking_task_finalize(infrared); + + if(!task_success) { + const char* signal_name = infrared_remote_get_signal_name( + infrared->remote, infrared->app_state.current_button_index); + infrared_show_error_message(infrared, "Failed to move\n\"%s\"", signal_name); scene_manager_search_and_switch_to_previous_scene( infrared->scene_manager, InfraredSceneRemoteList); } - - consumed = true; } + consumed = true; } return consumed; diff --git a/applications/main/infrared/scenes/infrared_scene_edit_rename.c b/applications/main/infrared/scenes/infrared_scene_edit_rename.c index 178690926..2763c2777 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_rename.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_rename.c @@ -3,6 +3,28 @@ #include #include +static int32_t infrared_scene_edit_rename_task_callback(void* context) { + InfraredApp* infrared = context; + InfraredAppState* app_state = &infrared->app_state; + const InfraredEditTarget edit_target = app_state->edit_target; + + bool success; + if(edit_target == InfraredEditTargetButton) { + furi_assert(app_state->current_button_index != InfraredButtonIndexNone); + success = infrared_remote_rename_signal( + infrared->remote, app_state->current_button_index, infrared->text_store[0]); + } else if(edit_target == InfraredEditTargetRemote) { + success = infrared_rename_current_remote(infrared, infrared->text_store[0]); + } else { + furi_crash(); + } + + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); + + return success; +} + void infrared_scene_edit_rename_on_enter(void* context) { InfraredApp* infrared = context; InfraredRemote* remote = infrared->remote; @@ -61,41 +83,31 @@ void infrared_scene_edit_rename_on_enter(void* context) { bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) { InfraredApp* infrared = context; - InfraredRemote* remote = infrared->remote; SceneManager* scene_manager = infrared->scene_manager; - InfraredAppState* app_state = &infrared->app_state; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == InfraredCustomEventTypeTextEditDone) { - bool success = false; - const InfraredEditTarget edit_target = app_state->edit_target; - if(edit_target == InfraredEditTargetButton) { - const int32_t current_button_index = app_state->current_button_index; - furi_assert(current_button_index != InfraredButtonIndexNone); - infrared_show_loading_popup(infrared, true); - success = infrared_remote_rename_signal( - remote, current_button_index, infrared->text_store[0]); - infrared_show_loading_popup(infrared, false); - app_state->current_button_index = InfraredButtonIndexNone; - } else if(edit_target == InfraredEditTargetRemote) { - success = infrared_rename_current_remote(infrared, infrared->text_store[0]); - } else { - furi_crash(); - } + // Rename a button or a remote in a separate thread + infrared_blocking_task_start(infrared, infrared_scene_edit_rename_task_callback); - if(success) { + } else if(event.event == InfraredCustomEventTypeTaskFinished) { + const bool task_success = infrared_blocking_task_finalize(infrared); + InfraredAppState* app_state = &infrared->app_state; + + if(task_success) { scene_manager_next_scene(scene_manager, InfraredSceneEditRenameDone); } else { - infrared_show_error_message( - infrared, - "Failed to\nrename %s", - edit_target == InfraredEditTargetButton ? "button" : "file"); + const char* edit_target_text = + app_state->edit_target == InfraredEditTargetButton ? "button" : "file"; + infrared_show_error_message(infrared, "Failed to\nrename %s", edit_target_text); scene_manager_search_and_switch_to_previous_scene( scene_manager, InfraredSceneRemoteList); } - consumed = true; + + app_state->current_button_index = InfraredButtonIndexNone; } + consumed = true; } return consumed; diff --git a/applications/main/infrared/scenes/infrared_scene_remote_list.c b/applications/main/infrared/scenes/infrared_scene_remote_list.c index 2276e270a..744409a7a 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote_list.c +++ b/applications/main/infrared/scenes/infrared_scene_remote_list.c @@ -1,41 +1,59 @@ #include "../infrared_app_i.h" -void infrared_scene_remote_list_on_enter(void* context) { +static int32_t infrared_scene_remote_list_task_callback(void* context) { InfraredApp* infrared = context; - SceneManager* scene_manager = infrared->scene_manager; - ViewDispatcher* view_dispatcher = infrared->view_dispatcher; - - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(view_dispatcher, InfraredViewStack); + const bool success = + infrared_remote_load(infrared->remote, furi_string_get_cstr(infrared->file_path)); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); + return success; +} +static void infrared_scene_remote_list_select_and_load(InfraredApp* infrared) { DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, INFRARED_APP_EXTENSION, &I_ir_10px); browser_options.base_path = INFRARED_APP_FOLDER; - while(dialog_file_browser_show( - infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options)) { - const char* file_path = furi_string_get_cstr(infrared->file_path); + const bool file_selected = dialog_file_browser_show( + infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options); - infrared_show_loading_popup(infrared, true); - const bool remote_loaded = infrared_remote_load(infrared->remote, file_path); - infrared_show_loading_popup(infrared, false); + if(file_selected) { + view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - if(remote_loaded) { - scene_manager_next_scene(scene_manager, InfraredSceneRemote); - return; - } else { - infrared_show_error_message(infrared, "Failed to load\n\"%s\"", file_path); - } + // Load the remote in a separate thread + infrared_blocking_task_start(infrared, infrared_scene_remote_list_task_callback); + + } else { + scene_manager_previous_scene(infrared->scene_manager); } +} - scene_manager_previous_scene(scene_manager); +void infrared_scene_remote_list_on_enter(void* context) { + InfraredApp* infrared = context; + infrared_scene_remote_list_select_and_load(infrared); } bool infrared_scene_remote_list_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); + InfraredApp* infrared = context; + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == InfraredCustomEventTypeTaskFinished) { + const bool task_success = infrared_blocking_task_finalize(infrared); + + if(task_success) { + scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote); + } else { + infrared_show_error_message( + infrared, "Failed to load\n\"%s\"", furi_string_get_cstr(infrared->file_path)); + infrared_scene_remote_list_select_and_load(infrared); + } + } + consumed = true; + } + return consumed; } diff --git a/applications/main/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c index f3408fba4..03a2bff01 100644 --- a/applications/main/infrared/scenes/infrared_scene_rpc.c +++ b/applications/main/infrared/scenes/infrared_scene_rpc.c @@ -9,6 +9,15 @@ typedef enum { InfraredRpcStateSending, } InfraredRpcState; +static int32_t infrared_scene_rpc_task_callback(void* context) { + InfraredApp* infrared = context; + const bool success = + infrared_remote_load(infrared->remote, furi_string_get_cstr(infrared->file_path)); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); + return success; +} + void infrared_scene_rpc_on_enter(void* context) { InfraredApp* infrared = context; Popup* popup = infrared->popup; @@ -21,7 +30,8 @@ void infrared_scene_rpc_on_enter(void* context) { popup_set_context(popup, context); popup_set_callback(popup, infrared_popup_closed_callback); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); + view_stack_add_view(infrared->view_stack, popup_get_view(infrared->popup)); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); scene_manager_set_scene_state(infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateIdle); @@ -33,76 +43,88 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - consumed = true; - InfraredRpcState state = + InfraredAppState* app_state = &infrared->app_state; + InfraredRpcState rpc_state = scene_manager_get_scene_state(infrared->scene_manager, InfraredSceneRpc); - if(event.event == InfraredCustomEventTypeBackPressed) { - view_dispatcher_stop(infrared->view_dispatcher); - } else if(event.event == InfraredCustomEventTypePopupClosed) { - view_dispatcher_stop(infrared->view_dispatcher); - } else if(event.event == InfraredCustomEventTypeRpcLoadFile) { - bool result = false; - if(state == InfraredRpcStateIdle) { - result = infrared_remote_load( - infrared->remote, furi_string_get_cstr(infrared->file_path)); - if(result) { - scene_manager_set_scene_state( - infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); - } + + if(event.event == InfraredCustomEventTypeRpcLoadFile) { + if(rpc_state == InfraredRpcStateIdle) { + // Load the remote in a separate thread + infrared_blocking_task_start(infrared, infrared_scene_rpc_task_callback); + } + + } else if(event.event == InfraredCustomEventTypeTaskFinished) { + const bool task_success = infrared_blocking_task_finalize(infrared); + + if(task_success) { + const char* remote_name = infrared_remote_get_name(infrared->remote); + infrared_text_store_set(infrared, 0, "loaded\n%s", remote_name); + scene_manager_set_scene_state( + infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); + + } else { + infrared_text_store_set( + infrared, 0, "failed to load\n%s", furi_string_get_cstr(infrared->file_path)); } - const char* remote_name = infrared_remote_get_name(infrared->remote); - infrared_text_store_set(infrared, 0, "loaded\n%s", remote_name); popup_set_text( infrared->popup, infrared->text_store[0], 89, 44, AlignCenter, AlignTop); - rpc_system_app_confirm(infrared->rpc_ctx, result); + rpc_system_app_confirm(infrared->rpc_ctx, task_success); + } else if( event.event == InfraredCustomEventTypeRpcButtonPressName || event.event == InfraredCustomEventTypeRpcButtonPressIndex) { bool result = false; - if(state == InfraredRpcStateLoaded) { + if(rpc_state == InfraredRpcStateLoaded) { if(event.event == InfraredCustomEventTypeRpcButtonPressName) { const char* button_name = furi_string_get_cstr(infrared->button_name); size_t index; const bool index_found = infrared_remote_get_signal_index(infrared->remote, button_name, &index); - infrared->app_state.current_button_index = - index_found ? (signed)index : InfraredButtonIndexNone; + app_state->current_button_index = index_found ? (signed)index : + InfraredButtonIndexNone; FURI_LOG_D(TAG, "Sending signal with name \"%s\"", button_name); } else { FURI_LOG_D( - TAG, - "Sending signal with index \"%ld\"", - infrared->app_state.current_button_index); + TAG, "Sending signal with index \"%ld\"", app_state->current_button_index); } if(infrared->app_state.current_button_index != InfraredButtonIndexNone) { - infrared_tx_start_button_index( - infrared, infrared->app_state.current_button_index); + infrared_tx_start_button_index(infrared, app_state->current_button_index); scene_manager_set_scene_state( infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateSending); result = true; } } rpc_system_app_confirm(infrared->rpc_ctx, result); + } else if(event.event == InfraredCustomEventTypeRpcButtonRelease) { bool result = false; - if(state == InfraredRpcStateSending) { + + if(rpc_state == InfraredRpcStateSending) { infrared_tx_stop(infrared); result = true; scene_manager_set_scene_state( infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); } + rpc_system_app_confirm(infrared->rpc_ctx, result); - } else if(event.event == InfraredCustomEventTypeRpcExit) { - scene_manager_stop(infrared->scene_manager); - view_dispatcher_stop(infrared->view_dispatcher); - rpc_system_app_confirm(infrared->rpc_ctx, true); - } else if(event.event == InfraredCustomEventTypeRpcSessionClose) { + + } else if( + event.event == InfraredCustomEventTypeRpcExit || + event.event == InfraredCustomEventTypeRpcSessionClose || + event.event == InfraredCustomEventTypePopupClosed) { scene_manager_stop(infrared->scene_manager); view_dispatcher_stop(infrared->view_dispatcher); + + if(event.event == InfraredCustomEventTypeRpcExit) { + rpc_system_app_confirm(infrared->rpc_ctx, true); + } } + + consumed = true; } + return consumed; } @@ -112,5 +134,7 @@ void infrared_scene_rpc_on_exit(void* context) { InfraredRpcStateSending) { infrared_tx_stop(infrared); } + + view_stack_remove_view(infrared->view_stack, popup_get_view(infrared->popup)); popup_reset(infrared->popup); } diff --git a/applications/main/infrared/scenes/infrared_scene_universal_ac.c b/applications/main/infrared/scenes/infrared_scene_universal_ac.c index 764a95189..b82bcc1f9 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_ac.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_ac.c @@ -3,8 +3,6 @@ #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_ac_on_enter(void* context) { - infrared_scene_universal_common_on_enter(context); - InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; @@ -122,16 +120,7 @@ void infrared_scene_universal_ac_on_enter(void* context) { button_panel_add_label(button_panel, 4, 10, FontPrimary, "AC remote"); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - - infrared_show_loading_popup(infrared, true); - bool success = infrared_brute_force_calculate_messages(brute_force); - infrared_show_loading_popup(infrared, false); - - if(!success) { - scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); - } + infrared_scene_universal_common_on_enter(context); } bool infrared_scene_universal_ac_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/infrared/scenes/infrared_scene_universal_audio.c b/applications/main/infrared/scenes/infrared_scene_universal_audio.c index 241a22bcb..a15b2ce99 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_audio.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_audio.c @@ -3,8 +3,6 @@ #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_audio_on_enter(void* context) { - infrared_scene_universal_common_on_enter(context); - InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; @@ -119,16 +117,7 @@ void infrared_scene_universal_audio_on_enter(void* context) { button_panel_add_label(button_panel, 1, 10, FontPrimary, "Mus. remote"); button_panel_add_icon(button_panel, 34, 56, &I_vol_ac_text_30x30); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - - infrared_show_loading_popup(infrared, true); - bool success = infrared_brute_force_calculate_messages(brute_force); - infrared_show_loading_popup(infrared, false); - - if(!success) { - scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); - } + infrared_scene_universal_common_on_enter(context); } bool infrared_scene_universal_audio_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/infrared/scenes/infrared_scene_universal_projector.c b/applications/main/infrared/scenes/infrared_scene_universal_projector.c index d8520deb3..c665444fb 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_projector.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_projector.c @@ -3,8 +3,6 @@ #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_projector_on_enter(void* context) { - infrared_scene_universal_common_on_enter(context); - InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; @@ -68,16 +66,7 @@ void infrared_scene_universal_projector_on_enter(void* context) { button_panel_add_label(button_panel, 3, 11, FontPrimary, "Proj. remote"); button_panel_add_icon(button_panel, 17, 72, &I_vol_ac_text_30x30); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - - infrared_show_loading_popup(infrared, true); - bool success = infrared_brute_force_calculate_messages(brute_force); - infrared_show_loading_popup(infrared, false); - - if(!success) { - scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); - } + infrared_scene_universal_common_on_enter(context); } bool infrared_scene_universal_projector_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/infrared/scenes/infrared_scene_universal_tv.c b/applications/main/infrared/scenes/infrared_scene_universal_tv.c index 6031205f5..16633e29c 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_tv.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_tv.c @@ -3,8 +3,6 @@ #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_tv_on_enter(void* context) { - infrared_scene_universal_common_on_enter(context); - InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; @@ -95,16 +93,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { button_panel_add_label(button_panel, 5, 10, FontPrimary, "TV remote"); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - - infrared_show_loading_popup(infrared, true); - bool success = infrared_brute_force_calculate_messages(brute_force); - infrared_show_loading_popup(infrared, false); - - if(!success) { - scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); - } + infrared_scene_universal_common_on_enter(context); } bool infrared_scene_universal_tv_on_event(void* context, SceneManagerEvent event) { From 2da05bfa6c58052baf6ced029a7d9cb49ba48005 Mon Sep 17 00:00:00 2001 From: Anna Prosvetova Date: Tue, 12 Mar 2024 00:24:29 +0100 Subject: [PATCH 08/30] Infrared: Add Fujitsu ASYG24KMTB --- .../infrared/resources/infrared/assets/ac.ir | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index 64d473def..4e1735246 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -788,3 +788,41 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 664 17757 3057 8901 528 469 550 1434 556 465 553 440 529 465 554 439 555 439 555 438 555 440 553 1435 552 443 550 470 524 1466 522 472 522 472 521 1467 523 1466 549 1440 549 1440 548 1440 548 445 549 445 549 445 549 446 548 470 524 471 523 471 523 471 523 472 522 472 522 473 522 472 523 472 548 446 549 445 550 445 548 446 548 446 548 446 548 446 548 447 547 470 524 471 523 471 523 471 523 471 523 474 519 474 521 474 521 473 522 473 546 447 547 1441 548 1442 546 1442 547 1442 546 2947 3023 8935 522 1466 522 472 522 498 496 498 495 499 496 498 496 498 521 473 522 471 523 1466 522 471 523 471 522 1466 523 471 523 1467 522 1467 522 1467 521 1493 495 1494 496 1493 521 473 522 472 522 471 523 472 522 471 523 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 499 495 499 495 499 495 499 496 498 522 473 522 472 523 471 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 473 521 473 521 473 521 499 495 500 494 500 495 499 496 498 522 2947 3023 8937 521 1468 520 473 521 473 521 473 521 473 521 473 521 473 521 474 520 474 520 1470 518 500 494 500 494 500 494 500 494 1494 521 1468 521 473 521 1468 520 1468 520 1469 520 1469 520 1470 518 1495 493 1496 493 1495 494 500 519 475 520 474 520 1469 519 1469 520 1469 519 474 520 474 520 475 519 477 517 500 494 1496 493 1496 493 1496 493 500 495 1495 519 475 518 475 519 475 518 476 518 475 519 1470 519 500 494 500 494 501 493 501 493 501 493 1499 490 1497 493 1497 492 1496 518 +# +# Model: Fujitsu ASYG24KMTB +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3302 1639 405 423 407 420 410 1234 405 421 409 1210 440 387 432 420 410 417 413 1231 408 1238 412 388 431 422 408 392 438 1233 406 1238 412 389 430 396 434 419 411 390 440 413 406 394 436 391 439 388 431 421 409 417 413 414 405 421 409 392 438 1233 406 421 409 417 413 414 405 395 435 418 412 388 432 421 409 1236 414 387 432 420 410 390 440 388 431 1213 437 1208 431 1239 411 1234 405 1213 437 1208 431 1214 436 1235 415 386 433 420 410 1234 405 422 408 418 412 389 431 396 434 1211 439 388 432 422 408 418 412 1233 406 1238 412 415 404 422 408 1237 413 388 432 422 408 1236 414 1205 434 1210 440 1205 434 392 438 390 440 1231 408 392 438 415 404 396 434 392 438 415 404 422 408 419 411 416 414 412 407 394 436 417 413 414 405 421 409 417 413 1232 407 419 411 390 440 1204 435 418 412 415 415 412 407 419 411 1208 431 421 409 418 412 388 432 421 409 392 438 415 404 395 435 1211 439 388 432 1213 437 416 414 386 433 1212 438 415 404 396 434 392 438 416 414 413 406 420 410 417 413 1231 409 418 412 389 430 1240 410 391 439 1205 434 420 410 390 440 387 432 420 410 417 413 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3293 1649 405 396 434 419 411 1207 432 395 435 1236 414 413 406 394 436 417 413 1232 407 1212 438 415 404 396 434 419 411 1234 405 1239 411 417 413 414 405 421 409 419 411 389 430 423 407 393 437 416 414 387 432 394 436 417 413 388 431 421 409 1236 414 387 432 421 409 418 412 414 405 422 408 418 412 415 404 1240 410 391 439 414 405 421 409 419 411 1234 405 1239 411 1208 431 1239 411 1235 404 1214 436 1210 440 1205 434 419 411 416 414 1204 435 418 412 415 404 396 434 393 437 1235 404 396 434 420 410 416 414 1231 408 1210 440 387 432 421 409 1235 415 414 405 395 435 418 412 1232 407 420 410 1208 431 422 408 1237 413 415 404 396 434 419 411 416 414 413 406 420 410 417 413 414 405 421 409 418 412 415 404 422 408 419 411 416 414 413 406 1238 412 415 404 422 408 1237 413 414 405 421 409 418 412 416 414 1231 408 418 412 415 404 422 408 419 411 416 414 413 406 420 410 1235 404 423 407 420 410 1234 405 422 408 1210 440 414 405 421 409 392 438 415 404 422 408 420 410 417 413 1231 408 419 411 416 414 413 406 1237 413 415 404 1239 411 417 413 1232 407 420 410 417 413 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3302 1614 440 414 405 421 409 1235 414 413 406 1238 411 415 404 422 408 419 411 1234 405 1240 409 418 412 415 404 422 408 1237 412 1232 407 420 410 417 413 414 405 422 408 419 411 416 414 413 406 420 410 417 413 414 405 421 409 418 412 415 404 1239 410 418 412 415 404 422 408 419 411 416 414 413 406 420 410 1234 405 422 408 419 411 416 414 414 405 1239 411 1207 432 1239 410 1235 414 1230 409 1236 413 1232 407 1237 412 415 415 412 407 1237 412 414 405 422 408 419 411 416 414 1231 408 419 411 416 414 413 406 1238 411 1206 433 421 409 418 412 1206 433 421 409 418 412 1206 433 1238 412 1207 432 1213 436 390 440 1232 407 420 410 417 413 414 405 421 409 418 412 415 404 422 408 419 411 416 414 413 406 421 409 418 412 415 404 422 408 419 411 1233 406 421 409 418 412 1232 407 420 410 418 412 389 430 422 408 1210 440 414 405 395 435 418 412 415 404 423 407 420 410 416 414 414 405 422 408 418 412 415 404 1240 409 1235 414 413 406 420 410 417 413 414 405 422 408 419 411 416 414 1231 408 418 412 415 404 1240 409 1209 440 387 432 1212 437 1234 405 1214 435 1209 440 1204 435 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3301 1615 439 415 404 422 408 1210 439 414 405 1239 410 416 414 414 405 395 435 1209 440 1205 434 419 411 417 413 414 405 1212 437 1207 432 422 408 419 411 416 414 414 405 421 409 418 412 415 404 422 408 393 437 416 414 413 406 420 410 417 413 1205 434 420 410 417 413 414 405 421 409 418 412 415 404 422 408 1210 439 414 405 422 408 419 411 417 413 1205 434 1236 413 1206 433 1211 438 1207 432 1212 437 1209 440 1204 435 418 412 415 415 1204 435 418 412 415 404 422 408 419 411 1208 441 412 407 420 410 417 413 1205 434 1237 412 414 405 422 408 1210 439 415 404 396 434 419 411 1207 432 1213 436 417 413 1205 434 420 410 417 413 1231 408 419 411 416 414 413 406 421 409 418 412 414 405 422 408 419 411 415 404 424 406 421 409 418 412 415 404 1239 410 417 413 414 405 1239 410 416 414 413 406 422 408 419 411 1233 406 421 409 418 412 415 404 423 407 419 411 416 414 413 406 1238 411 416 414 413 406 421 409 1235 414 1230 409 418 412 415 415 412 407 420 410 417 413 414 405 422 408 1236 413 413 406 421 409 1235 414 1204 435 1210 439 1206 433 1212 437 1207 432 395 435 1236 413 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3295 1646 408 420 410 390 440 1205 434 393 437 1234 405 421 409 419 411 415 415 1230 409 1210 440 414 405 421 409 418 412 1233 406 1212 437 416 414 413 406 394 436 418 412 415 404 422 408 393 437 416 414 413 406 420 410 417 413 414 405 421 409 1236 413 414 405 421 409 418 412 415 404 423 407 419 411 416 414 1231 408 418 412 415 415 412 407 421 409 1235 414 1204 435 1209 441 1205 434 1210 440 1205 434 1212 437 1207 432 395 435 419 411 1233 406 421 409 418 412 415 404 422 408 1238 411 415 404 396 434 419 411 1234 405 1239 410 417 413 414 405 1213 436 417 413 414 405 1213 436 1235 404 1214 435 1209 441 413 406 421 409 418 412 1206 433 394 436 418 412 388 431 422 408 419 411 415 415 386 433 420 410 418 412 414 405 422 408 419 411 389 430 1241 408 418 412 415 404 1240 409 417 413 414 405 395 435 419 411 1233 406 395 435 418 412 415 404 422 408 419 411 416 414 413 406 421 409 1236 413 413 406 394 436 1235 414 1230 409 418 412 415 404 422 408 420 410 417 413 414 405 421 409 1236 413 413 406 420 410 417 413 1232 407 1237 412 416 414 1230 409 1236 413 1205 434 1210 439 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3302 1640 404 423 407 420 410 1212 437 390 440 1234 405 395 435 392 438 415 415 1207 432 1242 407 420 410 391 439 414 405 1243 406 1241 408 392 438 415 415 386 433 393 437 390 440 414 405 396 434 419 411 389 441 412 407 420 410 390 440 387 432 1242 407 393 437 390 440 414 405 395 435 392 438 389 430 396 434 1240 409 417 413 414 405 395 435 419 411 1237 412 389 430 396 434 393 437 416 414 387 432 394 436 1212 437 389 441 1234 405 1217 432 1241 408 1213 436 1212 437 1210 439 From 5cfc773b7b32586a64141737c0abcce5d307f07a Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Tue, 12 Mar 2024 15:09:35 +0300 Subject: [PATCH 09/30] FL-3496: do not hardcode universal library names in CLI --- applications/main/infrared/infrared_cli.c | 49 ++++++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index c960ffa28..9829e59e9 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -10,8 +10,10 @@ #include "infrared_signal.h" #include "infrared_brute_force.h" -#define INFRARED_CLI_BUF_SIZE 10 -#define INFRARED_ASSETS_FOLDER "infrared/assets" +#define INFRARED_CLI_BUF_SIZE (10U) +#define INFRARED_CLI_FILE_NAME_SIZE (256U) +#define INFRARED_FILE_EXTENSION ".ir" +#define INFRARED_ASSETS_FOLDER EXT_PATH("infrared/assets") #define INFRARED_BRUTE_FORCE_DUMMY_INDEX 0 DICT_DEF2(dict_signals, FuriString*, FURI_STRING_OPLIST, int, M_DEFAULT_OPLIST) @@ -66,6 +68,37 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv } } +static void infrared_cli_print_universal_remotes(void) { + Storage* storage = furi_record_open(RECORD_STORAGE); + File* dir = storage_file_alloc(storage); + + do { + if(!storage_dir_open(dir, INFRARED_ASSETS_FOLDER)) break; + + FileInfo file_info; + char file_name[INFRARED_CLI_FILE_NAME_SIZE]; + + while(storage_dir_read(dir, &file_info, file_name, sizeof(file_name))) { + if(file_info.flags & FSF_DIRECTORY) { + continue; + } + + char* file_ext = strstr(file_name, INFRARED_FILE_EXTENSION); + if((file_ext == NULL) || (strcmp(file_ext, INFRARED_FILE_EXTENSION) != 0)) { + continue; + } + + *file_ext = '\0'; + printf("%s ", file_name); + } + + printf("\r\n"); + } while(false); + + storage_file_free(dir); + furi_record_close(RECORD_STORAGE); +} + static void infrared_cli_print_usage(void) { printf("Usage:\r\n"); printf("\tir rx [raw]\r\n"); @@ -85,8 +118,9 @@ static void infrared_cli_print_usage(void) { printf("\tir decode []\r\n"); printf("\tir universal \r\n"); printf("\tir universal list \r\n"); - // TODO FL-3496: Do not hardcode universal remote names - printf("\tAvailable universal remotes: tv audio ac projector\r\n"); + printf("\tAvailable universal remotes: "); + + infrared_cli_print_universal_remotes(); } static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) { @@ -365,7 +399,10 @@ static void infrared_cli_list_remote_signals(FuriString* remote_name) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); FuriString* remote_path = furi_string_alloc_printf( - "%s/%s.ir", EXT_PATH(INFRARED_ASSETS_FOLDER), furi_string_get_cstr(remote_name)); + "%s/%s%s", + INFRARED_ASSETS_FOLDER, + furi_string_get_cstr(remote_name), + INFRARED_FILE_EXTENSION); do { if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(remote_path))) { @@ -413,7 +450,7 @@ static void infrared_cli_brute_force_signals(Cli* cli, FuriString* remote_name, FuriString* signal_name) { InfraredBruteForce* brute_force = infrared_brute_force_alloc(); FuriString* remote_path = furi_string_alloc_printf( - "%s/%s.ir", EXT_PATH(INFRARED_ASSETS_FOLDER), furi_string_get_cstr(remote_name)); + "%s/%s.ir", INFRARED_ASSETS_FOLDER, furi_string_get_cstr(remote_name)); infrared_brute_force_set_db_filename(brute_force, furi_string_get_cstr(remote_path)); infrared_brute_force_add_record( From b27066b508316574df3027d6522e9e61cfd9b150 Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Tue, 12 Mar 2024 16:14:21 +0300 Subject: [PATCH 10/30] FL-3523: remove TODO, no changes necessary --- applications/main/infrared/infrared_cli.c | 1 - 1 file changed, 1 deletion(-) diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index 9829e59e9..8ac3e65b6 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -245,7 +245,6 @@ static bool infrared_cli_decode_raw_signal( size_t i; for(i = 0; i < raw_signal->timings_size; ++i) { - // TODO FL-3523: Any infrared_check_decoder_ready() magic? const InfraredMessage* message = infrared_decode(decoder, level, raw_signal->timings[i]); if(message) { From 3f7a070397ead88d3472a81264439ffed350ef9c Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Tue, 12 Mar 2024 16:18:48 +0300 Subject: [PATCH 11/30] FL-3767: remove TODO, no changes necessary --- applications/services/rpc/rpc.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 53b139dd4..1594da0b1 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -190,8 +190,6 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { furi_assert(session); furi_assert(istream->bytes_left); - /* TODO FL-3768 this function may be called after - marking the worker for termination */ if(session->terminate) { return false; } From 98c328f93f88f4ae350bd2385ee5e40bc46dff02 Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Tue, 12 Mar 2024 19:45:22 +0300 Subject: [PATCH 12/30] FL-3790: fix laggy TextInput by not adding it into a ViewStack --- applications/main/infrared/infrared_app.c | 12 ++++++++---- applications/main/infrared/infrared_app_i.h | 1 + .../common/infrared_scene_universal_common.c | 3 ++- .../infrared/scenes/infrared_scene_edit_delete.c | 7 ++----- .../infrared/scenes/infrared_scene_edit_move.c | 8 +++----- .../infrared/scenes/infrared_scene_edit_rename.c | 15 +++++---------- .../infrared/scenes/infrared_scene_remote_list.c | 3 --- .../main/infrared/scenes/infrared_scene_rpc.c | 7 ++----- 8 files changed, 23 insertions(+), 33 deletions(-) diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index 50534c660..4c89272de 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -189,6 +189,10 @@ static InfraredApp* infrared_alloc() { view_dispatcher_add_view( view_dispatcher, InfraredViewMove, infrared_move_view_get_view(infrared->move_view)); + infrared->loading = loading_alloc(); + view_dispatcher_add_view( + view_dispatcher, InfraredViewLoading, loading_get_view(infrared->loading)); + if(app_state->is_debug_enabled) { infrared->debug_view = infrared_debug_view_alloc(); view_dispatcher_add_view( @@ -198,7 +202,6 @@ static InfraredApp* infrared_alloc() { } infrared->button_panel = button_panel_alloc(); - infrared->loading = loading_alloc(); infrared->progress = infrared_progress_view_alloc(); return infrared; @@ -240,13 +243,15 @@ static void infrared_free(InfraredApp* infrared) { view_dispatcher_remove_view(view_dispatcher, InfraredViewMove); infrared_move_view_free(infrared->move_view); + view_dispatcher_remove_view(view_dispatcher, InfraredViewLoading); + loading_free(infrared->loading); + if(app_state->is_debug_enabled) { view_dispatcher_remove_view(view_dispatcher, InfraredViewDebugView); infrared_debug_view_free(infrared->debug_view); } button_panel_free(infrared->button_panel); - loading_free(infrared->loading); infrared_progress_view_free(infrared->progress); view_dispatcher_free(view_dispatcher); @@ -385,14 +390,13 @@ void infrared_tx_stop(InfraredApp* infrared) { } void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback) { - view_stack_add_view(infrared->view_stack, loading_get_view(infrared->loading)); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewLoading); furi_thread_set_callback(infrared->task_thread, callback); furi_thread_start(infrared->task_thread); } bool infrared_blocking_task_finalize(InfraredApp* infrared) { furi_thread_join(infrared->task_thread); - view_stack_remove_view(infrared->view_stack, loading_get_view(infrared->loading)); return furi_thread_get_return_code(infrared->task_thread); } diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index bccd58608..2c8047343 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -141,6 +141,7 @@ typedef enum { InfraredViewStack, InfraredViewDebugView, InfraredViewMove, + InfraredViewLoading, } InfraredView; /** diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c index 9fc48bd46..9bdcdc4a8 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c @@ -46,7 +46,6 @@ void infrared_scene_universal_common_on_enter(void* context) { InfraredApp* infrared = context; view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); view_stack_add_view(infrared->view_stack, button_panel_get_view(infrared->button_panel)); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); // Load universal remote data in background infrared_blocking_task_start(infrared, infrared_scene_universal_common_task_callback); @@ -98,6 +97,8 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e if(!task_success) { scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); + } else { + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); } } consumed = true; diff --git a/applications/main/infrared/scenes/infrared_scene_edit_delete.c b/applications/main/infrared/scenes/infrared_scene_edit_delete.c index 8dc4ab6f9..90a2633d3 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_delete.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_delete.c @@ -91,10 +91,7 @@ void infrared_scene_edit_delete_on_enter(void* context) { dialog_ex_set_result_callback(dialog_ex, infrared_scene_edit_delete_dialog_result_callback); dialog_ex_set_context(dialog_ex, context); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal); - view_stack_add_view(infrared->view_stack, dialog_ex_get_view(infrared->dialog_ex)); - - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx); } bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) { @@ -136,5 +133,5 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) void infrared_scene_edit_delete_on_exit(void* context) { InfraredApp* infrared = context; - view_stack_remove_view(infrared->view_stack, dialog_ex_get_view(infrared->dialog_ex)); + dialog_ex_reset(infrared->dialog_ex); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_move.c b/applications/main/infrared/scenes/infrared_scene_edit_move.c index 500f3d791..fcac1fc15 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_move.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_move.c @@ -38,10 +38,7 @@ void infrared_scene_edit_move_on_enter(void* context) { infrared_move_view_set_callback( infrared->move_view, infrared_scene_edit_move_button_callback, infrared); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal); - view_stack_add_view(infrared->view_stack, infrared_move_view_get_view(infrared->move_view)); - - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewMove); } bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) { @@ -62,6 +59,8 @@ bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) { infrared_show_error_message(infrared, "Failed to move\n\"%s\"", signal_name); scene_manager_search_and_switch_to_previous_scene( infrared->scene_manager, InfraredSceneRemoteList); + } else { + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewMove); } } consumed = true; @@ -72,6 +71,5 @@ bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) { void infrared_scene_edit_move_on_exit(void* context) { InfraredApp* infrared = context; - view_stack_remove_view(infrared->view_stack, infrared_move_view_get_view(infrared->move_view)); infrared_move_view_reset(infrared->move_view); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_rename.c b/applications/main/infrared/scenes/infrared_scene_edit_rename.c index 2763c2777..a546ad6e5 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_rename.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_rename.c @@ -75,10 +75,7 @@ void infrared_scene_edit_rename_on_enter(void* context) { enter_name_length, false); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal); - view_stack_add_view(infrared->view_stack, text_input_get_view(infrared->text_input)); - - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewTextInput); } bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) { @@ -117,12 +114,10 @@ void infrared_scene_edit_rename_on_exit(void* context) { InfraredApp* infrared = context; TextInput* text_input = infrared->text_input; - view_stack_remove_view(infrared->view_stack, text_input_get_view(text_input)); - - void* validator_context = text_input_get_validator_callback_context(text_input); - text_input_set_validator(text_input, NULL, NULL); - + ValidatorIsFile* validator_context = text_input_get_validator_callback_context(text_input); if(validator_context) { - validator_is_file_free((ValidatorIsFile*)validator_context); + validator_is_file_free(validator_context); } + + text_input_reset(text_input); } diff --git a/applications/main/infrared/scenes/infrared_scene_remote_list.c b/applications/main/infrared/scenes/infrared_scene_remote_list.c index 744409a7a..9c880ac2f 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote_list.c +++ b/applications/main/infrared/scenes/infrared_scene_remote_list.c @@ -18,9 +18,6 @@ static void infrared_scene_remote_list_select_and_load(InfraredApp* infrared) { infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options); if(file_selected) { - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - // Load the remote in a separate thread infrared_blocking_task_start(infrared, infrared_scene_remote_list_task_callback); diff --git a/applications/main/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c index 03a2bff01..4c263a117 100644 --- a/applications/main/infrared/scenes/infrared_scene_rpc.c +++ b/applications/main/infrared/scenes/infrared_scene_rpc.c @@ -30,11 +30,8 @@ void infrared_scene_rpc_on_enter(void* context) { popup_set_context(popup, context); popup_set_callback(popup, infrared_popup_closed_callback); - view_stack_add_view(infrared->view_stack, popup_get_view(infrared->popup)); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); scene_manager_set_scene_state(infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateIdle); - notification_message(infrared->notifications, &sequence_display_backlight_on); } @@ -69,6 +66,7 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { popup_set_text( infrared->popup, infrared->text_store[0], 89, 44, AlignCenter, AlignTop); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); rpc_system_app_confirm(infrared->rpc_ctx, task_success); @@ -135,6 +133,5 @@ void infrared_scene_rpc_on_exit(void* context) { infrared_tx_stop(infrared); } - view_stack_remove_view(infrared->view_stack, popup_get_view(infrared->popup)); popup_reset(infrared->popup); } From 75432346de09bd926ba2881ca8cfb7a84ffbd58f Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Tue, 12 Mar 2024 19:54:45 +0300 Subject: [PATCH 13/30] Improve documentation --- applications/services/gui/view_dispatcher.h | 4 +++- applications/services/gui/view_holder.h | 6 ++++++ applications/services/gui/view_stack.h | 4 +++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/applications/services/gui/view_dispatcher.h b/applications/services/gui/view_dispatcher.h index 67a3fc40b..bec15f6e3 100644 --- a/applications/services/gui/view_dispatcher.h +++ b/applications/services/gui/view_dispatcher.h @@ -1,6 +1,8 @@ /** * @file view_dispatcher.h - * GUI: ViewDispatcher API + * @brief GUI: ViewDispatcher API + * + * @warning Views added to a ViewDispatcher MUST NOT be in a ViewStack at the same time. */ #pragma once diff --git a/applications/services/gui/view_holder.h b/applications/services/gui/view_holder.h index 8e7db3b85..0bd29652c 100644 --- a/applications/services/gui/view_holder.h +++ b/applications/services/gui/view_holder.h @@ -1,3 +1,9 @@ +/** + * @file view_holder.h + * @brief GUI: ViewHolder API + * + * @warning View added to a ViewHolder MUST NOT be in a ViewStack at the same time. + */ #pragma once #include diff --git a/applications/services/gui/view_stack.h b/applications/services/gui/view_stack.h index ed17f682f..7387038b0 100644 --- a/applications/services/gui/view_stack.h +++ b/applications/services/gui/view_stack.h @@ -1,11 +1,13 @@ /** * @file view_stack.h - * GUI: ViewStack API + * @brief GUI: ViewStack API * * ViewStack accumulates several Views in one stack. * Draw callbacks are called sequenctially starting from * first added. Input callbacks are called in reverse order. * Consumed input is not passed on underlying layers. + * + * @warning Views added to a ViewStack MUST NOT be in a ViewDispatcher or a ViewHolder at the same time. */ #pragma once From 2aa85347ee22f0298aafeb880e55a95beb0e4fab Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Wed, 13 Mar 2024 12:28:08 +0300 Subject: [PATCH 14/30] Fix logical error in documentation --- applications/main/infrared/infrared_app_i.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index 2c8047343..1c074323a 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -214,8 +214,8 @@ void infrared_tx_stop(InfraredApp* infrared); /** * @brief Start a blocking task in a separate thread. * - * If a ViewStack is currently on screen, a busy "Hourglass" animation - * will be shown and no input will be accepted until completion. + * Before starting a blocking task, the current view will be replaced + * with a busy animation. All subsequent user input will be ignored. * * @param[in,out] infrared pointer to the application instance. * @param[in] callback pointer to the function to be run in the thread. @@ -223,10 +223,11 @@ void infrared_tx_stop(InfraredApp* infrared); void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback); /** - * @brief Wait for a blocking task to finish and receive the result. + * @brief Wait for a blocking task to finish and get the result. * - * Upon the completion of a blocking task, the busy animation will be hidden - * and input will be accepted again. + * The busy animation shown during the infrared_blocking_task_start() call + * will NOT be hidden and WILL remain on screen. If another view is needed + * (e.g. to display the results), the caller code MUST set it explicitly. * * @param[in,out] infrared pointer to the application instance. * @return true if the blocking task finished successfully, false otherwise. From eb013ab6ec4c5ae43de0cff567288dad270e099b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 15 Mar 2024 04:08:52 +0300 Subject: [PATCH 15/30] revert usb cdc config changes to verify issue 3452 --- targets/f7/furi_hal/furi_hal_usb_cdc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/targets/f7/furi_hal/furi_hal_usb_cdc.c b/targets/f7/furi_hal/furi_hal_usb_cdc.c index 014c98bad..0cde83f14 100644 --- a/targets/f7/furi_hal/furi_hal_usb_cdc.c +++ b/targets/f7/furi_hal/furi_hal_usb_cdc.c @@ -7,13 +7,13 @@ #include "usb.h" #include "usb_cdc.h" -#define CDC0_RXD_EP 0x02 +#define CDC0_RXD_EP 0x01 #define CDC0_TXD_EP 0x82 -#define CDC0_NTF_EP 0x81 +#define CDC0_NTF_EP 0x83 #define CDC1_RXD_EP 0x04 -#define CDC1_TXD_EP 0x84 -#define CDC1_NTF_EP 0x83 +#define CDC1_TXD_EP 0x85 +#define CDC1_NTF_EP 0x86 #define CDC_NTF_SZ 0x08 From 17dd089b5a4b47c3c82de33ddf7a1ed8540659e1 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 15 Mar 2024 04:27:37 +0300 Subject: [PATCH 16/30] sync scenes --- .../infrared/scenes/infrared_scene_universal_fan.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/applications/main/infrared/scenes/infrared_scene_universal_fan.c b/applications/main/infrared/scenes/infrared_scene_universal_fan.c index 9074b5508..5fdea5a0a 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_fan.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_fan.c @@ -3,8 +3,6 @@ #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_fan_on_enter(void* context) { - infrared_scene_universal_common_on_enter(context); - InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; @@ -97,16 +95,7 @@ void infrared_scene_universal_fan_on_enter(void* context) { button_panel_add_label(button_panel, 5, 11, FontPrimary, "Fan remote"); button_panel_add_icon(button_panel, 34, 68, &I_speed_text_30x30); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - - infrared_show_loading_popup(infrared, true); - bool success = infrared_brute_force_calculate_messages(brute_force); - infrared_show_loading_popup(infrared, false); - - if(!success) { - scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); - } + infrared_scene_universal_common_on_enter(context); } bool infrared_scene_universal_fan_on_event(void* context, SceneManagerEvent event) { From d573cc06da498744afc9979082167514677fd0f1 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 15 Mar 2024 07:09:16 +0300 Subject: [PATCH 17/30] js modules updates by Willy-JL and Sil333033 --- applications/system/js_app/application.fam | 8 + .../js_app/examples/apps/Scripts/dialog.js | 5 +- .../js_app/examples/apps/Scripts/gpio.js | 62 ++++ .../system/js_app/modules/js_dialog.c | 53 ++++ applications/system/js_app/modules/js_gpio.c | 272 ++++++++++++++++++ 5 files changed, 399 insertions(+), 1 deletion(-) create mode 100644 applications/system/js_app/examples/apps/Scripts/gpio.js create mode 100644 applications/system/js_app/modules/js_gpio.c diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 0b9b1f51f..961834dcc 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -86,3 +86,11 @@ App( requires=["js_app"], sources=["modules/js_subghz/*.c"], ) + +App( + appid="js_gpio", + apptype=FlipperAppType.PLUGIN, + entry_point="js_gpio_ep", + requires=["js_app"], + sources=["modules/js_gpio.c"], +) diff --git a/applications/system/js_app/examples/apps/Scripts/dialog.js b/applications/system/js_app/examples/apps/Scripts/dialog.js index 9fc44f8b9..4c5b0af20 100644 --- a/applications/system/js_app/examples/apps/Scripts/dialog.js +++ b/applications/system/js_app/examples/apps/Scripts/dialog.js @@ -7,13 +7,16 @@ let dialog_params = ({ header: "Test_header", text: "Test_text", button_left: "Left", - button_right: "Right", + button_right: "Files", button_center: "OK" }); let result2 = dialog.custom(dialog_params); if (result2 === "") { print("Back is pressed"); +} else if (result2 === "Files") { + let result3 = dialog.pickFile("/ext", "*"); + print("Selected", result3); } else { print(result2, "is pressed"); } diff --git a/applications/system/js_app/examples/apps/Scripts/gpio.js b/applications/system/js_app/examples/apps/Scripts/gpio.js new file mode 100644 index 000000000..02b55a3ad --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/gpio.js @@ -0,0 +1,62 @@ +let gpio = require("gpio"); + +// initialize pins +gpio.init("PC3", "outputPushPull", "up"); // pin, mode, pull +print("PC3 is initialized as outputPushPull with pull-up"); + +gpio.init("PC1", "input", "down"); // pin, mode, pull +print("PC1 is initialized as input with pull-down"); + +// let led on PC3 blink +gpio.write("PC3", true); // high +delay(1000); +gpio.write("PC3", false); // low +delay(1000); +gpio.write("PC3", true); // high +delay(1000); +gpio.write("PC3", false); // low + +// read value from PC1 and write it to PC3 +while (true) { + let value = gpio.read("PC1"); + gpio.write("PC3", value); + + value ? print("PC1 is high") : print("PC1 is low"); + + delay(100); +} + + +// possible pins https://docs.flipper.net/gpio-and-modules#miFsS +// "PA7" aka 2 +// "PA6" aka 3 +// "PA4" aka 4 +// "PB3" aka 5 +// "PB2" aka 6 +// "PC3" aka 7 +// "PA14" aka 10 +// "PA13" aka 12 +// "PB6" aka 13 +// "PB7" aka 14 +// "PC1" aka 15 +// "PC0" aka 16 +// "PB14" aka 17 + +// possible modes +// "input" +// "outputPushPull" +// "outputOpenDrain" +// "altFunctionPushPull" +// "altFunctionOpenDrain" +// "analog" +// "interruptRise" +// "interruptFall" +// "interruptRiseFall" +// "eventRise" +// "eventFall" +// "eventRiseFall" + +// possible pull +// "no" +// "up" +// "down" \ No newline at end of file diff --git a/applications/system/js_app/modules/js_dialog.c b/applications/system/js_app/modules/js_dialog.c index 34de6d641..a457d700c 100644 --- a/applications/system/js_app/modules/js_dialog.c +++ b/applications/system/js_app/modules/js_dialog.c @@ -1,6 +1,7 @@ #include #include "../js_modules.h" #include +#include static bool js_dialog_msg_parse_params(struct mjs* mjs, const char** hdr, const char** msg) { size_t num_args = mjs_nargs(mjs); @@ -128,10 +129,62 @@ static void js_dialog_custom(struct mjs* mjs) { } } +static void js_dialog_pick_file(struct mjs* mjs) { + if(mjs_nargs(mjs) != 2) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Wrong arguments"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + mjs_val_t base_path_obj = mjs_arg(mjs, 0); + if(!mjs_is_string(base_path_obj)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Base path must be a string"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + size_t base_path_len = 0; + const char* base_path = mjs_get_string(mjs, &base_path_obj, &base_path_len); + if((base_path_len == 0) || (base_path == NULL)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Bad base path argument"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + mjs_val_t extension_obj = mjs_arg(mjs, 1); + if(!mjs_is_string(extension_obj)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Extension must be a string"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + size_t extension_len = 0; + const char* extension = mjs_get_string(mjs, &extension_obj, &extension_len); + if((extension_len == 0) || (extension == NULL)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Bad extension argument"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + const DialogsFileBrowserOptions browser_options = { + .extension = extension, + .icon = &I_Apps_10px, + .base_path = base_path, + }; + FuriString* path = furi_string_alloc_set(base_path); + if(dialog_file_browser_show(dialogs, path, path, &browser_options)) { + mjs_return(mjs, mjs_mk_string(mjs, furi_string_get_cstr(path), ~0, true)); + } else { + mjs_return(mjs, MJS_UNDEFINED); + } + furi_string_free(path); + furi_record_close(RECORD_DIALOGS); +} + static void* js_dialog_create(struct mjs* mjs, mjs_val_t* object) { mjs_val_t dialog_obj = mjs_mk_object(mjs); mjs_set(mjs, dialog_obj, "message", ~0, MJS_MK_FN(js_dialog_message)); mjs_set(mjs, dialog_obj, "custom", ~0, MJS_MK_FN(js_dialog_custom)); + mjs_set(mjs, dialog_obj, "pickFile", ~0, MJS_MK_FN(js_dialog_pick_file)); *object = dialog_obj; return (void*)1; diff --git a/applications/system/js_app/modules/js_gpio.c b/applications/system/js_app/modules/js_gpio.c new file mode 100644 index 000000000..8f6158418 --- /dev/null +++ b/applications/system/js_app/modules/js_gpio.c @@ -0,0 +1,272 @@ +#include "../js_modules.h" +#include +#include +#include + +typedef struct { + const GpioPin* pin; + const char* name; +} GpioPinCtx; + +static const GpioPinCtx js_gpio_pins[] = { + {.pin = &gpio_ext_pa7, .name = "PA7"}, // 2 + {.pin = &gpio_ext_pa6, .name = "PA6"}, // 3 + {.pin = &gpio_ext_pa4, .name = "PA4"}, // 4 + {.pin = &gpio_ext_pb3, .name = "PB3"}, // 5 + {.pin = &gpio_ext_pb2, .name = "PB2"}, // 6 + {.pin = &gpio_ext_pc3, .name = "PC3"}, // 7 + {.pin = &gpio_swclk, .name = "PA14"}, // 10 + {.pin = &gpio_swdio, .name = "PA13"}, // 12 + {.pin = &gpio_usart_tx, .name = "PB6"}, // 13 + {.pin = &gpio_usart_rx, .name = "PB7"}, // 14 + {.pin = &gpio_ext_pc1, .name = "PC1"}, // 15 + {.pin = &gpio_ext_pc0, .name = "PC0"}, // 16 + {.pin = &gpio_ibutton, .name = "PB14"}, // 17 +}; + +bool js_gpio_get_gpio_pull(const char* pull, GpioPull* value) { + if(strcmp(pull, "no") == 0) { + *value = GpioPullNo; + return true; + } else if(strcmp(pull, "up") == 0) { + *value = GpioPullUp; + return true; + } else if(strcmp(pull, "down") == 0) { + *value = GpioPullDown; + return true; + } else { + *value = GpioPullNo; + return true; + } + return false; +} + +bool js_gpio_get_gpio_mode(const char* mode, GpioMode* value) { + if(strcmp(mode, "input") == 0) { + *value = GpioModeInput; + return true; + } else if(strcmp(mode, "outputPushPull") == 0) { + *value = GpioModeOutputPushPull; + return true; + } else if(strcmp(mode, "outputOpenDrain") == 0) { + *value = GpioModeOutputOpenDrain; + return true; + } else if(strcmp(mode, "altFunctionPushPull") == 0) { + *value = GpioModeAltFunctionPushPull; + return true; + } else if(strcmp(mode, "altFunctionOpenDrain") == 0) { + *value = GpioModeAltFunctionOpenDrain; + return true; + } else if(strcmp(mode, "analog") == 0) { + *value = GpioModeAnalog; + return true; + } else if(strcmp(mode, "interruptRise") == 0) { + *value = GpioModeInterruptRise; + return true; + } else if(strcmp(mode, "interruptFall") == 0) { + *value = GpioModeInterruptFall; + return true; + } else if(strcmp(mode, "interruptRiseFall") == 0) { + *value = GpioModeInterruptRiseFall; + return true; + } else if(strcmp(mode, "eventRise") == 0) { + *value = GpioModeEventRise; + return true; + } else if(strcmp(mode, "eventFall") == 0) { + *value = GpioModeEventFall; + return true; + } else if(strcmp(mode, "eventRiseFall") == 0) { + *value = GpioModeEventRiseFall; + return true; + } else { + return false; + } +} + +const GpioPin* js_gpio_get_gpio_pin(const char* name) { + for(size_t i = 0; i < COUNT_OF(js_gpio_pins); i++) { + if(strcmp(js_gpio_pins[i].name, name) == 0) { + return js_gpio_pins[i].pin; + } + } + return NULL; +} + +static void js_gpio_init(struct mjs* mjs) { + mjs_val_t pin_arg = mjs_arg(mjs, 0); + mjs_val_t mode_arg = mjs_arg(mjs, 1); + mjs_val_t pull_arg = mjs_arg(mjs, 2); + + if(!mjs_is_string(pin_arg)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Argument must be a string"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + const char* pin_name = mjs_get_string(mjs, &pin_arg, NULL); + if(!pin_name) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Failed to get pin name"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + if(!mjs_is_string(mode_arg)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Argument must be a string"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + const char* mode_name = mjs_get_string(mjs, &mode_arg, NULL); + if(!mode_name) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Failed to get mode name"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + if(!mjs_is_string(pull_arg)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Argument must be a string"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + const char* pull_name = mjs_get_string(mjs, &pull_arg, NULL); + if(!pull_name) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Failed to get pull name"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + const GpioPin* gpio_pin = js_gpio_get_gpio_pin(pin_name); + if(gpio_pin == NULL) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid pin name"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + GpioMode gpio_mode; + if(!js_gpio_get_gpio_mode(mode_name, &gpio_mode)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid mode name"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + GpioPull gpio_pull; + if(!js_gpio_get_gpio_pull(pull_name, &gpio_pull)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid pull name"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + expansion_disable(furi_record_open(RECORD_EXPANSION)); + furi_record_close(RECORD_EXPANSION); + + furi_hal_gpio_init(gpio_pin, gpio_mode, gpio_pull, GpioSpeedVeryHigh); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_gpio_write(struct mjs* mjs) { + mjs_val_t pin_arg = mjs_arg(mjs, 0); + mjs_val_t value_arg = mjs_arg(mjs, 1); + + if(!mjs_is_string(pin_arg)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Argument must be a string"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + const char* pin_name = mjs_get_string(mjs, &pin_arg, NULL); + if(!pin_name) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Failed to get pin name"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + if(!mjs_is_boolean(value_arg)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Argument must be a boolean"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + bool value = mjs_get_bool(mjs, value_arg); + + const GpioPin* gpio_pin = js_gpio_get_gpio_pin(pin_name); + + if(gpio_pin == NULL) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid pin name"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + furi_hal_gpio_write(gpio_pin, value); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_gpio_read(struct mjs* mjs) { + mjs_val_t pin_arg = mjs_arg(mjs, 0); + + if(!mjs_is_string(pin_arg)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Argument must be a string"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + const char* pin_name = mjs_get_string(mjs, &pin_arg, NULL); + if(!pin_name) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Failed to get pin name"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + const GpioPin* gpio_pin = js_gpio_get_gpio_pin(pin_name); + + if(gpio_pin == NULL) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid pin name"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + bool value = furi_hal_gpio_read(gpio_pin); + + mjs_return(mjs, mjs_mk_boolean(mjs, value)); +} + +static void* js_gpio_create(struct mjs* mjs, mjs_val_t* object) { + mjs_val_t gpio_obj = mjs_mk_object(mjs); + mjs_set(mjs, gpio_obj, "init", ~0, MJS_MK_FN(js_gpio_init)); + mjs_set(mjs, gpio_obj, "write", ~0, MJS_MK_FN(js_gpio_write)); + mjs_set(mjs, gpio_obj, "read", ~0, MJS_MK_FN(js_gpio_read)); + *object = gpio_obj; + + return (void*)1; +} + +static void js_gpio_destroy(void* inst) { + UNUSED(inst); + + // loop through all pins and reset them to analog mode + for(size_t i = 0; i < COUNT_OF(js_gpio_pins); i++) { + furi_hal_gpio_write(js_gpio_pins[i].pin, false); + furi_hal_gpio_init(js_gpio_pins[i].pin, GpioModeAnalog, GpioPullNo, GpioSpeedVeryHigh); + } + + expansion_enable(furi_record_open(RECORD_EXPANSION)); + furi_record_close(RECORD_EXPANSION); +} + +static const JsModuleDescriptor js_gpio_desc = { + "gpio", + js_gpio_create, + js_gpio_destroy, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_gpio_desc, +}; + +const FlipperAppPluginDescriptor* js_gpio_ep(void) { + return &plugin_descriptor; +} \ No newline at end of file From f88da8b0c3705a6fa0ac390f5ab162f5d9b6709a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 15 Mar 2024 09:32:42 +0300 Subject: [PATCH 18/30] test swap em4100 t5577 blocks (issue 3463 ofw) --- lib/lfrfid/protocols/protocol_em4100.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/lfrfid/protocols/protocol_em4100.c b/lib/lfrfid/protocols/protocol_em4100.c index 05952110d..2a9e60b00 100644 --- a/lib/lfrfid/protocols/protocol_em4100.c +++ b/lib/lfrfid/protocols/protocol_em4100.c @@ -318,8 +318,8 @@ bool protocol_em4100_write_data(ProtocolEM4100* protocol, void* data) { request->t5577.block[0] = (LFRFID_T5577_MODULATION_MANCHESTER | protocol_em4100_get_t5577_bitrate(protocol) | (2 << LFRFID_T5577_MAXBLOCK_SHIFT)); - request->t5577.block[1] = protocol->encoded_data; - request->t5577.block[2] = protocol->encoded_data >> 32; + request->t5577.block[1] = protocol->encoded_data >> 32; + request->t5577.block[2] = protocol->encoded_data; request->t5577.blocks_to_write = 3; result = true; } From ce67472dc4a884b79648d369f266314e2f94bc2a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 15 Mar 2024 09:33:27 +0300 Subject: [PATCH 19/30] use non prefixed names for regular files with random name --- .../main/ibutton/scenes/ibutton_scene_save_name.c | 2 +- applications/main/lfrfid/scenes/lfrfid_scene_save_name.c | 2 +- .../nfc/helpers/protocol_support/nfc_protocol_support.c | 2 +- lib/toolbox/name_generator.c | 8 ++++++++ lib/toolbox/name_generator.h | 3 +++ targets/f7/api_symbols.csv | 1 + 6 files changed, 15 insertions(+), 3 deletions(-) diff --git a/applications/main/ibutton/scenes/ibutton_scene_save_name.c b/applications/main/ibutton/scenes/ibutton_scene_save_name.c index e6236dc35..f48ae60d3 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_save_name.c +++ b/applications/main/ibutton/scenes/ibutton_scene_save_name.c @@ -17,7 +17,7 @@ void ibutton_scene_save_name_on_enter(void* context) { const bool is_new_file = furi_string_empty(ibutton->file_path); if(is_new_file) { - name_generator_make_auto( + name_generator_make_auto_basic( ibutton->key_name, IBUTTON_KEY_NAME_SIZE, IBUTTON_APP_FILENAME_PREFIX); } diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c index 3a38e213d..c6845b44a 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c @@ -12,7 +12,7 @@ void lfrfid_scene_save_name_on_enter(void* context) { if(key_name_is_empty) { furi_string_set(app->file_path, LFRFID_APP_FOLDER); - name_generator_make_auto( + name_generator_make_auto_basic( app->text_store, LFRFID_TEXT_STORE_SIZE, LFRFID_APP_FILENAME_PREFIX); furi_string_set(folder_path, LFRFID_APP_FOLDER); diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index bf562bf58..549f1ca94 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -481,7 +481,7 @@ static void nfc_protocol_support_scene_save_name_on_enter(NfcApp* instance) { bool name_is_empty = furi_string_empty(instance->file_name); if(name_is_empty) { furi_string_set(instance->file_path, NFC_APP_FOLDER); - name_generator_make_auto( + name_generator_make_auto_basic( instance->text_store, NFC_TEXT_STORE_SIZE, NFC_APP_FILENAME_PREFIX); furi_string_set(folder_path, NFC_APP_FOLDER); } else { diff --git a/lib/toolbox/name_generator.c b/lib/toolbox/name_generator.c index 9748a7648..5986c0ed7 100644 --- a/lib/toolbox/name_generator.c +++ b/lib/toolbox/name_generator.c @@ -60,6 +60,14 @@ void name_generator_make_auto(char* name, size_t max_name_size, const char* pref name_generator_make_auto_datetime(name, max_name_size, prefix, NULL); } +void name_generator_make_auto_basic(char* name, size_t max_name_size, const char* prefix) { + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDetailedFilename)) { + name_generator_make_detailed_datetime(name, max_name_size, prefix, NULL); + } else { + name_generator_make_random(name, max_name_size); + } +} + void name_generator_make_random_prefixed(char* name, size_t max_name_size, const char* prefix) { furi_assert(name); furi_assert(max_name_size); diff --git a/lib/toolbox/name_generator.h b/lib/toolbox/name_generator.h index e6c443688..b990381c3 100644 --- a/lib/toolbox/name_generator.h +++ b/lib/toolbox/name_generator.h @@ -21,6 +21,9 @@ void name_generator_make_auto_datetime( const char* prefix, DateTime* custom_time); +// Generate name without prefix in random names +void name_generator_make_auto_basic(char* name, size_t max_name_size, const char* prefix); + /** Generates random name * * @param name buffer to write random name diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index f2e97a5e0..8ca458eed 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2645,6 +2645,7 @@ Function,-,music_worker_set_volume,void,"MusicWorker*, float" Function,-,music_worker_start,void,MusicWorker* Function,-,music_worker_stop,void,MusicWorker* Function,+,name_generator_make_auto,void,"char*, size_t, const char*" +Function,+,name_generator_make_auto_basic,void,"char*, size_t, const char*" Function,+,name_generator_make_auto_datetime,void,"char*, size_t, const char*, DateTime*" Function,+,name_generator_make_detailed,void,"char*, size_t, const char*" Function,+,name_generator_make_detailed_datetime,void,"char*, size_t, const char*, DateTime*" From 7e9ecdf394c34ab55f33ef164fed98b270618ee1 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 16 Mar 2024 02:56:55 +0300 Subject: [PATCH 20/30] the real update --- lib/toolbox/name_generator.c | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/lib/toolbox/name_generator.c b/lib/toolbox/name_generator.c index 5986c0ed7..c0decd10d 100644 --- a/lib/toolbox/name_generator.c +++ b/lib/toolbox/name_generator.c @@ -13,35 +13,25 @@ const char* const name_generator_left[] = { "little", "liquid", "unknown", - "thin", - "thick", + "cheeky", + "tricky", + "sneaky", + "quick", + "quantum", + "kurwa", "great", - "my", + "smart", "mini", "ultra", - "haupt", "small", "random", "strange", }; const char* const name_generator_right[] = { - "maslina", - "sus", - "anomalija", - "artefact", - "monolit", - "burer", - "sidorovich", - "habar", - "radar", - "borov", - "pda", - "konserva", - "aptechka", - "door", - "thing", - "stuff", + "maslina", "sus", "anomalija", "artefact", "bobr", "chomik", "sidorovich", + "stalker", "kit", "habar", "jezyk", "borov", "juzyk", "konserva", + "aptechka", "door", "zalaz", "breeky", "pingwin", "kot", }; void name_generator_make_auto_datetime( From a0dc2b5cfcbf156668dac45f22c3a9f979389390 Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Mon, 18 Mar 2024 20:59:35 +0300 Subject: [PATCH 21/30] Fix DS1990 emulation --- lib/one_wire/one_wire_slave.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/one_wire/one_wire_slave.c b/lib/one_wire/one_wire_slave.c index 733b36e30..6fff75214 100644 --- a/lib/one_wire/one_wire_slave.c +++ b/lib/one_wire/one_wire_slave.c @@ -158,6 +158,8 @@ static inline bool onewire_slave_receive_and_process_command(OneWireSlave* bus) static inline bool onewire_slave_bus_start(OneWireSlave* bus) { FURI_CRITICAL_ENTER(); + + furi_hal_gpio_disable_int_callback(bus->gpio_pin); furi_hal_gpio_init(bus->gpio_pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); while(onewire_slave_receive_and_process_command(bus)) @@ -166,6 +168,8 @@ static inline bool onewire_slave_bus_start(OneWireSlave* bus) { const bool result = (bus->error == OneWireSlaveErrorNone); furi_hal_gpio_init(bus->gpio_pin, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_enable_int_callback(bus->gpio_pin); + FURI_CRITICAL_EXIT(); return result; From a09ec4d9769f96c49256315918bcedc71a365335 Mon Sep 17 00:00:00 2001 From: Anna Prosvetova Date: Tue, 19 Mar 2024 10:19:31 +0100 Subject: [PATCH 22/30] Infrared: Add Fujitsu ASYG24KMTB (#3513) --- .../infrared/resources/infrared/assets/ac.ir | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index 64d473def..4e1735246 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -788,3 +788,41 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 664 17757 3057 8901 528 469 550 1434 556 465 553 440 529 465 554 439 555 439 555 438 555 440 553 1435 552 443 550 470 524 1466 522 472 522 472 521 1467 523 1466 549 1440 549 1440 548 1440 548 445 549 445 549 445 549 446 548 470 524 471 523 471 523 471 523 472 522 472 522 473 522 472 523 472 548 446 549 445 550 445 548 446 548 446 548 446 548 446 548 447 547 470 524 471 523 471 523 471 523 471 523 474 519 474 521 474 521 473 522 473 546 447 547 1441 548 1442 546 1442 547 1442 546 2947 3023 8935 522 1466 522 472 522 498 496 498 495 499 496 498 496 498 521 473 522 471 523 1466 522 471 523 471 522 1466 523 471 523 1467 522 1467 522 1467 521 1493 495 1494 496 1493 521 473 522 472 522 471 523 472 522 471 523 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 499 495 499 495 499 495 499 496 498 522 473 522 472 523 471 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 473 521 473 521 473 521 499 495 500 494 500 495 499 496 498 522 2947 3023 8937 521 1468 520 473 521 473 521 473 521 473 521 473 521 473 521 474 520 474 520 1470 518 500 494 500 494 500 494 500 494 1494 521 1468 521 473 521 1468 520 1468 520 1469 520 1469 520 1470 518 1495 493 1496 493 1495 494 500 519 475 520 474 520 1469 519 1469 520 1469 519 474 520 474 520 475 519 477 517 500 494 1496 493 1496 493 1496 493 500 495 1495 519 475 518 475 519 475 518 476 518 475 519 1470 519 500 494 500 494 501 493 501 493 501 493 1499 490 1497 493 1497 492 1496 518 +# +# Model: Fujitsu ASYG24KMTB +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3302 1639 405 423 407 420 410 1234 405 421 409 1210 440 387 432 420 410 417 413 1231 408 1238 412 388 431 422 408 392 438 1233 406 1238 412 389 430 396 434 419 411 390 440 413 406 394 436 391 439 388 431 421 409 417 413 414 405 421 409 392 438 1233 406 421 409 417 413 414 405 395 435 418 412 388 432 421 409 1236 414 387 432 420 410 390 440 388 431 1213 437 1208 431 1239 411 1234 405 1213 437 1208 431 1214 436 1235 415 386 433 420 410 1234 405 422 408 418 412 389 431 396 434 1211 439 388 432 422 408 418 412 1233 406 1238 412 415 404 422 408 1237 413 388 432 422 408 1236 414 1205 434 1210 440 1205 434 392 438 390 440 1231 408 392 438 415 404 396 434 392 438 415 404 422 408 419 411 416 414 412 407 394 436 417 413 414 405 421 409 417 413 1232 407 419 411 390 440 1204 435 418 412 415 415 412 407 419 411 1208 431 421 409 418 412 388 432 421 409 392 438 415 404 395 435 1211 439 388 432 1213 437 416 414 386 433 1212 438 415 404 396 434 392 438 416 414 413 406 420 410 417 413 1231 409 418 412 389 430 1240 410 391 439 1205 434 420 410 390 440 387 432 420 410 417 413 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3293 1649 405 396 434 419 411 1207 432 395 435 1236 414 413 406 394 436 417 413 1232 407 1212 438 415 404 396 434 419 411 1234 405 1239 411 417 413 414 405 421 409 419 411 389 430 423 407 393 437 416 414 387 432 394 436 417 413 388 431 421 409 1236 414 387 432 421 409 418 412 414 405 422 408 418 412 415 404 1240 410 391 439 414 405 421 409 419 411 1234 405 1239 411 1208 431 1239 411 1235 404 1214 436 1210 440 1205 434 419 411 416 414 1204 435 418 412 415 404 396 434 393 437 1235 404 396 434 420 410 416 414 1231 408 1210 440 387 432 421 409 1235 415 414 405 395 435 418 412 1232 407 420 410 1208 431 422 408 1237 413 415 404 396 434 419 411 416 414 413 406 420 410 417 413 414 405 421 409 418 412 415 404 422 408 419 411 416 414 413 406 1238 412 415 404 422 408 1237 413 414 405 421 409 418 412 416 414 1231 408 418 412 415 404 422 408 419 411 416 414 413 406 420 410 1235 404 423 407 420 410 1234 405 422 408 1210 440 414 405 421 409 392 438 415 404 422 408 420 410 417 413 1231 408 419 411 416 414 413 406 1237 413 415 404 1239 411 417 413 1232 407 420 410 417 413 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3302 1614 440 414 405 421 409 1235 414 413 406 1238 411 415 404 422 408 419 411 1234 405 1240 409 418 412 415 404 422 408 1237 412 1232 407 420 410 417 413 414 405 422 408 419 411 416 414 413 406 420 410 417 413 414 405 421 409 418 412 415 404 1239 410 418 412 415 404 422 408 419 411 416 414 413 406 420 410 1234 405 422 408 419 411 416 414 414 405 1239 411 1207 432 1239 410 1235 414 1230 409 1236 413 1232 407 1237 412 415 415 412 407 1237 412 414 405 422 408 419 411 416 414 1231 408 419 411 416 414 413 406 1238 411 1206 433 421 409 418 412 1206 433 421 409 418 412 1206 433 1238 412 1207 432 1213 436 390 440 1232 407 420 410 417 413 414 405 421 409 418 412 415 404 422 408 419 411 416 414 413 406 421 409 418 412 415 404 422 408 419 411 1233 406 421 409 418 412 1232 407 420 410 418 412 389 430 422 408 1210 440 414 405 395 435 418 412 415 404 423 407 420 410 416 414 414 405 422 408 418 412 415 404 1240 409 1235 414 413 406 420 410 417 413 414 405 422 408 419 411 416 414 1231 408 418 412 415 404 1240 409 1209 440 387 432 1212 437 1234 405 1214 435 1209 440 1204 435 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3301 1615 439 415 404 422 408 1210 439 414 405 1239 410 416 414 414 405 395 435 1209 440 1205 434 419 411 417 413 414 405 1212 437 1207 432 422 408 419 411 416 414 414 405 421 409 418 412 415 404 422 408 393 437 416 414 413 406 420 410 417 413 1205 434 420 410 417 413 414 405 421 409 418 412 415 404 422 408 1210 439 414 405 422 408 419 411 417 413 1205 434 1236 413 1206 433 1211 438 1207 432 1212 437 1209 440 1204 435 418 412 415 415 1204 435 418 412 415 404 422 408 419 411 1208 441 412 407 420 410 417 413 1205 434 1237 412 414 405 422 408 1210 439 415 404 396 434 419 411 1207 432 1213 436 417 413 1205 434 420 410 417 413 1231 408 419 411 416 414 413 406 421 409 418 412 414 405 422 408 419 411 415 404 424 406 421 409 418 412 415 404 1239 410 417 413 414 405 1239 410 416 414 413 406 422 408 419 411 1233 406 421 409 418 412 415 404 423 407 419 411 416 414 413 406 1238 411 416 414 413 406 421 409 1235 414 1230 409 418 412 415 415 412 407 420 410 417 413 414 405 422 408 1236 413 413 406 421 409 1235 414 1204 435 1210 439 1206 433 1212 437 1207 432 395 435 1236 413 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3295 1646 408 420 410 390 440 1205 434 393 437 1234 405 421 409 419 411 415 415 1230 409 1210 440 414 405 421 409 418 412 1233 406 1212 437 416 414 413 406 394 436 418 412 415 404 422 408 393 437 416 414 413 406 420 410 417 413 414 405 421 409 1236 413 414 405 421 409 418 412 415 404 423 407 419 411 416 414 1231 408 418 412 415 415 412 407 421 409 1235 414 1204 435 1209 441 1205 434 1210 440 1205 434 1212 437 1207 432 395 435 419 411 1233 406 421 409 418 412 415 404 422 408 1238 411 415 404 396 434 419 411 1234 405 1239 410 417 413 414 405 1213 436 417 413 414 405 1213 436 1235 404 1214 435 1209 441 413 406 421 409 418 412 1206 433 394 436 418 412 388 431 422 408 419 411 415 415 386 433 420 410 418 412 414 405 422 408 419 411 389 430 1241 408 418 412 415 404 1240 409 417 413 414 405 395 435 419 411 1233 406 395 435 418 412 415 404 422 408 419 411 416 414 413 406 421 409 1236 413 413 406 394 436 1235 414 1230 409 418 412 415 404 422 408 420 410 417 413 414 405 421 409 1236 413 413 406 420 410 417 413 1232 407 1237 412 416 414 1230 409 1236 413 1205 434 1210 439 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3302 1640 404 423 407 420 410 1212 437 390 440 1234 405 395 435 392 438 415 415 1207 432 1242 407 420 410 391 439 414 405 1243 406 1241 408 392 438 415 415 386 433 393 437 390 440 414 405 396 434 419 411 389 441 412 407 420 410 390 440 387 432 1242 407 393 437 390 440 414 405 395 435 392 438 389 430 396 434 1240 409 417 413 414 405 395 435 419 411 1237 412 389 430 396 434 393 437 416 414 387 432 394 436 1212 437 389 441 1234 405 1217 432 1241 408 1213 436 1212 437 1210 439 From 71111b0d2f1805ea736ca7a24b8ecead8ca53f4d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 20 Mar 2024 12:44:56 +0300 Subject: [PATCH 23/30] sync anims with OFW [FL-3776] Add the Freedom_2_dolphins animation --- .ci_files/anims_ofw.txt | 37 +++++++++++------- .../L3_Freedom_2_dolphins_128x64/frame_0.png | Bin 0 -> 1059 bytes .../L3_Freedom_2_dolphins_128x64/frame_1.png | Bin 0 -> 1114 bytes .../L3_Freedom_2_dolphins_128x64/frame_10.png | Bin 0 -> 1451 bytes .../L3_Freedom_2_dolphins_128x64/frame_11.png | Bin 0 -> 1300 bytes .../L3_Freedom_2_dolphins_128x64/frame_12.png | Bin 0 -> 1666 bytes .../L3_Freedom_2_dolphins_128x64/frame_13.png | Bin 0 -> 1946 bytes .../L3_Freedom_2_dolphins_128x64/frame_14.png | Bin 0 -> 1929 bytes .../L3_Freedom_2_dolphins_128x64/frame_15.png | Bin 0 -> 1929 bytes .../L3_Freedom_2_dolphins_128x64/frame_16.png | Bin 0 -> 1850 bytes .../L3_Freedom_2_dolphins_128x64/frame_17.png | Bin 0 -> 1872 bytes .../L3_Freedom_2_dolphins_128x64/frame_18.png | Bin 0 -> 1715 bytes .../L3_Freedom_2_dolphins_128x64/frame_19.png | Bin 0 -> 2446 bytes .../L3_Freedom_2_dolphins_128x64/frame_2.png | Bin 0 -> 1592 bytes .../L3_Freedom_2_dolphins_128x64/frame_20.png | Bin 0 -> 2383 bytes .../L3_Freedom_2_dolphins_128x64/frame_21.png | Bin 0 -> 2399 bytes .../L3_Freedom_2_dolphins_128x64/frame_22.png | Bin 0 -> 2396 bytes .../L3_Freedom_2_dolphins_128x64/frame_23.png | Bin 0 -> 2398 bytes .../L3_Freedom_2_dolphins_128x64/frame_24.png | Bin 0 -> 2394 bytes .../L3_Freedom_2_dolphins_128x64/frame_25.png | Bin 0 -> 2418 bytes .../L3_Freedom_2_dolphins_128x64/frame_26.png | Bin 0 -> 2434 bytes .../L3_Freedom_2_dolphins_128x64/frame_27.png | Bin 0 -> 2434 bytes .../L3_Freedom_2_dolphins_128x64/frame_28.png | Bin 0 -> 1835 bytes .../L3_Freedom_2_dolphins_128x64/frame_29.png | Bin 0 -> 1852 bytes .../L3_Freedom_2_dolphins_128x64/frame_3.png | Bin 0 -> 1553 bytes .../L3_Freedom_2_dolphins_128x64/frame_30.png | Bin 0 -> 1829 bytes .../L3_Freedom_2_dolphins_128x64/frame_31.png | Bin 0 -> 1882 bytes .../L3_Freedom_2_dolphins_128x64/frame_32.png | Bin 0 -> 1888 bytes .../L3_Freedom_2_dolphins_128x64/frame_33.png | Bin 0 -> 1878 bytes .../L3_Freedom_2_dolphins_128x64/frame_34.png | Bin 0 -> 2056 bytes .../L3_Freedom_2_dolphins_128x64/frame_35.png | Bin 0 -> 2048 bytes .../L3_Freedom_2_dolphins_128x64/frame_36.png | Bin 0 -> 2042 bytes .../L3_Freedom_2_dolphins_128x64/frame_37.png | Bin 0 -> 2062 bytes .../L3_Freedom_2_dolphins_128x64/frame_38.png | Bin 0 -> 2073 bytes .../L3_Freedom_2_dolphins_128x64/frame_39.png | Bin 0 -> 2000 bytes .../L3_Freedom_2_dolphins_128x64/frame_4.png | Bin 0 -> 1551 bytes .../L3_Freedom_2_dolphins_128x64/frame_40.png | Bin 0 -> 2127 bytes .../L3_Freedom_2_dolphins_128x64/frame_41.png | Bin 0 -> 1602 bytes .../L3_Freedom_2_dolphins_128x64/frame_42.png | Bin 0 -> 1463 bytes .../L3_Freedom_2_dolphins_128x64/frame_43.png | Bin 0 -> 1556 bytes .../L3_Freedom_2_dolphins_128x64/frame_44.png | Bin 0 -> 1850 bytes .../L3_Freedom_2_dolphins_128x64/frame_45.png | Bin 0 -> 2130 bytes .../L3_Freedom_2_dolphins_128x64/frame_46.png | Bin 0 -> 2139 bytes .../L3_Freedom_2_dolphins_128x64/frame_47.png | Bin 0 -> 2155 bytes .../L3_Freedom_2_dolphins_128x64/frame_48.png | Bin 0 -> 2179 bytes .../L3_Freedom_2_dolphins_128x64/frame_49.png | Bin 0 -> 2132 bytes .../L3_Freedom_2_dolphins_128x64/frame_5.png | Bin 0 -> 1490 bytes .../L3_Freedom_2_dolphins_128x64/frame_50.png | Bin 0 -> 2191 bytes .../L3_Freedom_2_dolphins_128x64/frame_51.png | Bin 0 -> 2165 bytes .../L3_Freedom_2_dolphins_128x64/frame_52.png | Bin 0 -> 2205 bytes .../L3_Freedom_2_dolphins_128x64/frame_53.png | Bin 0 -> 2151 bytes .../L3_Freedom_2_dolphins_128x64/frame_54.png | Bin 0 -> 2146 bytes .../L3_Freedom_2_dolphins_128x64/frame_55.png | Bin 0 -> 1952 bytes .../L3_Freedom_2_dolphins_128x64/frame_56.png | Bin 0 -> 1878 bytes .../L3_Freedom_2_dolphins_128x64/frame_6.png | Bin 0 -> 1592 bytes .../L3_Freedom_2_dolphins_128x64/frame_7.png | Bin 0 -> 1537 bytes .../L3_Freedom_2_dolphins_128x64/frame_8.png | Bin 0 -> 1460 bytes .../L3_Freedom_2_dolphins_128x64/frame_9.png | Bin 0 -> 1490 bytes .../L3_Freedom_2_dolphins_128x64/meta.txt | 23 +++++++++++ assets/dolphin/external/manifest.txt | 17 +++++--- 60 files changed, 57 insertions(+), 20 deletions(-) create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_0.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_1.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_10.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_11.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_12.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_13.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_14.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_15.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_16.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_17.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_18.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_19.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_2.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_20.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_21.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_22.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_23.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_24.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_25.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_26.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_27.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_28.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_29.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_3.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_30.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_31.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_32.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_33.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_34.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_35.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_36.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_37.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_38.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_39.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_4.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_40.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_41.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_42.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_43.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_44.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_45.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_46.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_47.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_48.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_49.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_5.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_50.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_51.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_52.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_53.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_54.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_55.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_56.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_6.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_7.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_8.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_9.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/meta.txt diff --git a/.ci_files/anims_ofw.txt b/.ci_files/anims_ofw.txt index a061c48ce..c0e1741a0 100644 --- a/.ci_files/anims_ofw.txt +++ b/.ci_files/anims_ofw.txt @@ -69,7 +69,7 @@ Min butthurt: 0 Max butthurt: 9 Min level: 1 Max level: 3 -Weight: 4 +Weight: 3 Name: L1_Painting_128x64 Min butthurt: 0 @@ -85,26 +85,33 @@ Min level: 1 Max level: 3 Weight: 3 +Name: L1_Senpai_128x64 +Min butthurt: 0 +Max butthurt: 5 +Min level: 1 +Max level: 3 +Weight: 3 + Name: L1_Kaiju_128x64 Min butthurt: 0 Max butthurt: 10 Min level: 1 Max level: 3 -Weight: 4 +Weight: 3 Name: L1_My_dude_128x64 Min butthurt: 0 Max butthurt: 8 Min level: 1 Max level: 3 -Weight: 4 +Weight: 3 Name: L2_Wake_up_128x64 Min butthurt: 0 Max butthurt: 12 Min level: 2 Max level: 3 -Weight: 4 +Weight: 3 Name: L2_Furippa2_128x64 Min butthurt: 0 @@ -132,7 +139,7 @@ Min butthurt: 0 Max butthurt: 8 Min level: 2 Max level: 3 -Weight: 4 +Weight: 3 Name: L3_Furippa3_128x64 Min butthurt: 0 @@ -155,30 +162,30 @@ Min level: 3 Max level: 3 Weight: 3 -Name: L1_Senpai_128x64 -Min butthurt: 0 -Max butthurt: 5 -Min level: 1 -Max level: 3 -Weight: 4 - Name: L1_Sad_song_128x64 Min butthurt: 8 Max butthurt: 13 Min level: 1 Max level: 3 -Weight: 4 +Weight: 3 Name: L2_Coding_in_the_shell_128x64 Min butthurt: 0 Max butthurt: 12 Min level: 2 Max level: 3 -Weight: 4 +Weight: 3 Name: L2_Secret_door_128x64 Min butthurt: 0 Max butthurt: 12 Min level: 2 Max level: 3 -Weight: 4 +Weight: 3 + +Name: L3_Freedom_2_dolphins_128x64 +Min butthurt: 0 +Max butthurt: 12 +Min level: 3 +Max level: 3 +Weight: 5 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_0.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_0.png new file mode 100755 index 0000000000000000000000000000000000000000..7d42cb57db999d20125d8ff2d5061a40c975d5a9 GIT binary patch literal 1059 zcmV+;1l;?HP)i+PaAH2SL*d z0B!()`@W%YT^HcbR#V$V+2?6Y0i{FEzzEbfQFq&Hn{oK(oFhd7ZXBLtc~R?FZFDr}1JH>VfUnKP zF_aKmw$)JM@E*%`GxCj)q4wfoGv#bhr4TWn@A#P3c$+< zYrI8J;GzPyC6WkUQiGjJnu3ST*ddhjsWrW{)!H_DETRBBjIczyV=Tvj9bDhpn&dEo z2t)yRPUursm9(uhftCoU_fj9$wl@W^+a5uL^04&L$`PJi8^Bfs-ASPIJ|Z)1gllBHMNS@c(GB74S&Cz*2)Imr_e zLavYQeB$SSo=xg%=l0m!+qRxuOpn8(VjclbKqCTeREUmw5@;RTywR+sqmjXjp5tVD+G%W*91DA1J8(a$3 zPT+E;8Me0a6r2PXG6E2^oF?7fb(>aY;R$CiKnj3`a_mU^C?crs`mhyJ0KZs-XH7() z+IoRSkU2m_Z(_z}*i;pP6SP(PCOARN2*B`XtzgdTl_E$1EI`T)aGRN=02abk37#U@N&vl^Gdr+a1X~H0 zH-ZAtIzy4CkGO1EJz|~$*c7W=AWs7~<>ME=50-(9027&A6}66twNc<}xc7y$LSCfxdbTap zZw-4RK}LYh;SP#i&H|Dc;DoUJtsw=lIouCUwWI(JVk8Gh0USg|fPW0PGgw1Kh7`bI dw46_U{tZ$_s-d*!*B1Z)002ovPDHLkV1ibi;B^21 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_1.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_1.png new file mode 100755 index 0000000000000000000000000000000000000000..25b4609c37949e1dac85ed4d8246a39d4329b66c GIT binary patch literal 1114 zcmV-g1f~0lP)f6h5tB;dvoIhGf-kM%}pb3Oo_cmc%P z+#Ev*L9(rf8b|b4j+2pZh75I-2%9Nqb6dfcj6Z7rSph^Ev64s;;C{GOi`!%pfhd3| zBUTeFf(92Auq~NH@X{LGsibK{*o+-QIiK3oTU)Db-eXY(5Mjhhv=y^*4A{Z_%C;nj z8APB8AaX*lva6((&IBY8P~WvaR@*KL;BM~-GL(m{4=G2u@@#;$A{b5rweK16w2@2n zsD5$sH*43*@u&i@8jvLltU!Bp%eB`cA~#6r+)jNS)M3ytT*hxb3s~WNDFRdN zI80T5l|BQ`_&M5^8XuPvaghN#X4d(wJ?`gfh4KGF2v)N8i95^wNloO)81W=?4k9Pn zAtB`Y7|tht|If8a-R;~Fdq>;$lZzQ~cvhB2fG1!Pfi)^*XL%C1IHMx}t6z!;T$I3AOSDn6>VRwqRR+GHZD}J(W&e9)q(mEGwnSW720jg3#&K;)(RyumrjvSPXRSwzs=^=2!S0N#*7 zv?ZKdA_CPmijM>rN&p{xgh?gjOV6;pfQshLCMW^8A!XlLL{I_nO#~%?4QROmZsSRM0dViKpFkfCc8MUp0G`fn5!6}0W~BE7{qJ6s z01~iM1SNnZ%!puxFiHT47!kp$Yj~~#=+DIa&%4s@2lW3|V->-wdj(YmBqgdkC;=o! z6Tu2f02i^W#48T-7C{N%23W78&G424dM7wqQs~|Poso`|F(c8TBEU_M-UmpBOBzcA zFtb{pa-b85c3ZhIQYpAc0IN7OWn0=`wI{nMwQXQ(A;O>l!p^CaJ7X78Rp26k%*oid ziiD~PW<~HLb+Q|e7avcd&CAs}x|9QC5DCC{MuJsW%r7T_SwztD4T4ye3$R;&)afq1 z!Le5G3E}KEuYg=FtO0q7%QbNLO9?*U2}{#?xkhPHrF%3*QLMxA9hf}V2g z$BT$Vwwa5hbr`e(EP|_%^2#>gx&@5jeX$}FUgMbA2GR)NDbC%=Oa)Tc!XJXA45dU- g0(cmb^Qpi811bdjh~;$8#{d8T07*qoM6N<$g5bIA4FCWD literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_10.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_10.png new file mode 100755 index 0000000000000000000000000000000000000000..575df3b9f5bd672203836637106fa68fba69af7a GIT binary patch literal 1451 zcmV;c1yuTpP)=Kp_r`fzHNq1;8(v^7MM}uovr872WPssi|XZ+O$ z$9Q37n^L=fHmuMY;6I~9jNkXQ#k|AcE#QCHp}BU8>)!=8#air=sNYtj>BPCw89L+D zgXL?4;QExt(YKj_7gnGNo2@f;|D16&Pr!|%ax5=uAM2Ig&G-Ox-~~{t zb2AJ@1WC6Z)Htfgay<-vcZg8EsL+`*Hn$awMEq6r&k~?giA+?I0L$T4EpD?D2}A-^ zDUmI=ngk7;SHQaLM1q&rASae*Es7mqorSLS$B0$3E#7|9V}S7@bMuD#|F zxvgS!n;ThRX@@~t#p@rz8xueS7x7yR-`?Y%Nsvn5RGMiafhh~{00(@Lc6}tB`RQ#- z>CDAM9PzPXqQ-CSv7FHi<9#6nGgp#+EI#ow(WJK#wU%*GVW}6`(5q!XIG-n2Ia99b;_FVm7)R6+{K-30IsW`?WN^vVEb*hu?bNYLx`p({8D5K+8>09h$n z28k>lfofX?OafO75RIx&fkamPyA0QX19^~jQ< z?^)S^6>kDSAP8K&hxIt2BFEJeR>ua{b=e7k-PsCQ%dw;S%}Ud7|67m%7y?vRv8>=+ zan_z{EisSwzY7VD+5#{vCE6@_(?U;QD@kydNcjR-4t}h}=#4Cukt;}$ej}&~y99mB zSP^}s%3=R}CXOY*F5A#3R+Hf97=Xeqo`Hg7RIX?=J0=L71?&n>I=CZIyd=no6Q-X9 zRARJ}xdSPzB*>~Hk~e~+iP)J0qesA5eT~9Hg8q91Zvvcf4xnMiS->hHRK)~8xdm9U z`kX-{K?cnKKEUqA2goPr>X<-F5mRIU;Q>xCHiy+ln*r?lGcT3AJwRLKtKHrxPA0De z=|~yFV>|Ui$cYnrDtRSXib(CbXRCO%m%hdL0BiJ~Ez^1##v3HM?*p8U*~i5*y^m(_ zh|S0>;Ma{G8GpqQE;e*VR`5Z9Q_{T%s^Wqw67;f0>H`-5P}ShCLUiSimX@own~`nc z_3g&DV#Yj7JHlmd7e{dHg^(!vdIwIQGH(}GY&qO@G7o-Y3&7AODeEw3c4oj;2tD@v zDGy-1yLVC{2_G1>KU;BoUy=f)T|LPPgI3f9dZS0Tfp;a)BQ)}C1){fg`}a>lGzNH$ zTxT9Vh?t4Y+dzypz{h_b>j77njv}f9WvG$y zNd0)*RFP#Q8@T(1S+#O+J{_H?Y~yS1Qsd$#d{yA-_5c9<{~oRhiYQ1bF=@Y+`pqi) z&KOfv2dXGU-4*C@_YYfVvrCveoY!X=h??Y;;5-0Fia%qM`ukB)rhfnc002ovPDHLk FV1k?psWt!r literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_11.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_11.png new file mode 100755 index 0000000000000000000000000000000000000000..2cfc582ef9f3c1dba187a96c4941ba73195647ec GIT binary patch literal 1300 zcmV+v1?&2WP)bKpfHgREeh0XZ% z;PoX!aC7$d(T-Vx7dD_S$QhUctxeQvTWvEA|D1EANWkqQaxX6`kF`c;b3OnqyZ~Zt zu8yII(6g-twU6k%T!o=;28C)xgw2$*xl}Nt_@nMWOMpltJP}C(ybgD2@t8~`=n^2x z2yaA_pn{7E*p^Hrcu503sidihuo*jqaz2&Q>sYOAtM?)jAi@YwNGHZ~57@!&$<~O& z3?vW<5P3sqv8$x*Jrn4Wfch@2;ca`D0H4+)$WR{EHF_FhW^Mpq5|~Ax^gSb1+QDe2c*_5H(rW}-2Jk+K&mkCIB`BW4};9$6uzGX6~l$cCN)<>)7_@VptrW748w>8=xV9HY#L?y9o3SZPC!=($UBe1xq>@ zDg!l(e=4NY--`%bl)%uIXjVwo0XYs%8F+(~(k!B~y11cIfo8S;&8G7nF3kePZ0>JeXc58#{x()%N zpw$N9B!EMJP)ORK9TR~=fDMqe0UomwhX5PVMuS}>NYe_|v|Yaqy%Mp#z7lB&Q2W(w z5B)D-Hwl)cfJeW1(<9NJ_D`_Jz z5kTYY*#?GoL)w#&fe^E$XYW8z_rVe++zGG(7M4?|BLfL4M#<3IOYh;5gk>UdCNyPo z*h*Lho%FVHg{4wh(h2m$>QpkbMl^bF7PZ$ca3O$4F6QQtLY`*s-uxW0lA>1{4iTV~ z`+peG+jzvO+MPN+p&>?91*0Um-gC&p#BxFQsG;eJR=CB!gpkv;5vlv z1ZWq0Ot7&`o2>>uod_OU;Px+s%(N+QlMZyav~!G#z};QUl_Z!+1CJ^IfX}h*_fhO9 z4?XP|!OCbbn+DhyLde0R%?9FiCJC!ZwyTs|weD;hcxV8xleXH)BkNIX?;yddC@_)+ ze((K#OvURkv_|V0Mw)ab0Vr`BWIvA*G%7_MTQixat$j9hRZ;Y zieYAs=)WCBM3YF{6rv81`DvinhNKfQ^N6!IUlby{gBl^ok>VeWO87` literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_12.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_12.png new file mode 100755 index 0000000000000000000000000000000000000000..4aa135cd6ba8c0d708869660226ed8983c6d2dc0 GIT binary patch literal 1666 zcmV-|27UR7P)qEWFLL%2t%G zt~s)8(eGuii?jjQNtJt9) z;onhpcf02GW?Jes${9TdeKo#)7!8D3_uhf)B`L-wj-5B>IMq>Py{mC$=s z&TM#B!hdTGD!;@5tT!P+EkUvmh_0W2YE0QsYp+$=GGV?oKW$hH-qW0Oa}1(<5v3+M}>-+w-TAN)+$gF){+!wNbm5k@ZKq7vFhBRlVma39GqGW=&afSdxdVZEa4Q4zq-U71%6`$tgSJGH;}JSh$^Qpp}> z7{ClpF#|J|?~ZB32;d1+!3aem8Lngi3e-LB`*{)fu@j|T2u2_jLNqIfD?nuOEXrr( z&Hm1K2I5tjer*2M{wINUfK@0OI8x~&a;a@s=VV5my;@lid5av$n=`tqsri`#m?Ba5 zV5BIEQ6#EL`cN7hfmtV-k@N0ztrc&X$>6i=TO)|%@I3O&m?h&&9iZPlwvjBav=)b~ zqhK6`$;RZed3WT-Ucs1pJXmgEo}uJ8)UOIv>FC^xVj&ozq+jh^@E^sTiGv0+5482Mu? z933q^uTPuV6~gK1Mvao@e$OG%QAX2X2fyyj*4b@MgaIB@m}lU$cQ{| z%qrPp#QD8C{w&qSQCPNZ+dM@vut;!({3$r9d@r4$erSE$ubR@%@Y(7O@*dWDJmL^D z;ME}%s8z#^qOXiqA!in>k~T9uR#`sscrWMQRnZI&rTT}^dp^qjBT+fp^`x~oa%&L3 zq-=!MdNKp56e{D60-X$$${_RjUst%rOCd<{G{*Y-@uN@)_iCuG0zF^kIA{J0RszvL zr1Y7&pOtI+O+s@%V>Y=pHnuD~Onu7>Tt&Bn3(j^;sQRhfPf zZzM45*(~=)+`$7gJeHfId7^$9-0c*wY~7Al!up5^ksTlL16B)Ah+P!*Zg~o*qRPCF zZ0@rBtL9Y{sK}F@_Y9s~{p^w1s0g-3z^=?&5%II_y|in+e>V&zy&xm%=j`XReeZ_1 z?*pi5UV!^JZ;orcU6=U`gSXp~^oz0(=Wc z*>*OS{lWQ8AR>S>Ln?Zq6M@wruI~At1SEmqI38t{AI-%ODR` zGFS=C)d+A!?>hsY5kU3+s9M*W|0N8CXH+7J8AD_s z{n#oem{Bykf-!g}gG!VO|HFv=wQ8h%D+D8gs$j@C`s<8};kLbgH}Gt*`_H0p#*p74 zfLisdyg@{#iYN+v6K5lsjA-{VWF7A9&%@E89=Yom%50e0m#sh3OS2KCB8#%8XbfAg z>OiXvD`?XGy8n(mS$UYOpht+}RWLKsM0@n;b37a95iv#iSxZOjcyb|*aAs7E7VV?e zUw>8jzzYf77w|T@@5hAdFR~wQ0ANK730p=#^Cco~G1^@s6 M07*qoM6N<$g45PMl>h($ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_13.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_13.png new file mode 100755 index 0000000000000000000000000000000000000000..aae2827e8596d168ae734a93f96135397a266c5d GIT binary patch literal 1946 zcmV;L2W9w)P)Y zdW-`S~);eccSa;c^Bq}RMUS{ zUo-VrkExzV^4PiQ3}8h*iuA}^z4I*X=D#O9AF2h z*nyqKcgKuk2JjTBpu

4&USe4A?!E&v_a5HHp$x0ucy?5bdhr3@kGF6yqcEroSVe zVezWWb4dOee@UPopbcsb>@<2uE!F2$naHWLN3DxR-l9PAWJX7}G`~v#GNhG05Gl%{ zi$qmPpH;>>u&bl#lsjdem1xo21h5f(D(*puxs-7YZe+Qo&s<-UV{jB#0P*dkVl{2>-@wU(Z7 zqI(Dx<5%YTw&-SX>dl+5_yCId2<*mX8ND^8qaFPp4UO>50Om~)y*k1zC-R~(Yh;a% z{=GVWce`83fGaqz>$*HcA)rjKLjD-68s95t)^pbU`nhUJ%j|ha3AOH>-XL$WI^r!3 zu>;R$mZ44VNR>Ws@M260i zLnc77Yqe0#CZ~5(-t&&$;;P6HKIDj5qNwFX*BH50Mq(W%l09Xul!A8#U^iMm`-IwW zIil3ndfpn3&a1HGcxypxKy7PV7hnlz&{g{J}J+ejLtU2}4Tma*oLOjBZlS{JtAjR$++f z%6v?#h`JR`Y?s==ZdBN|9^a7`SK;Z;z87HUV1ote1piGSYGZaqRn#Y2K#_NM`O-Qt zEPU1Y%y?!$OcHoUd!*6X7JU*s=hvi`c|LNMmwdo$kM3}Y>>QQOpaWS0bRb(Y*mo^I zelsYGh>T@rJ*9>tME&(Ax+eryNB8#v&>(b8-pg)ArVC*8$jH#TWaqK?5|Xuw6#}Ts z`};S8B0&{0SbfPYslO4#q&c1bGo_%Ba&amPL z`o6Uv4Cl&5I?Ub!IDn@_l*;CJEl)nvkqN4$qQe+D9nmPlJ$7oz z@D>v}LDl${oE2mv@zcQ`E;;d0K1m(Ka4nze?LC-jIWhI zL@?WNSRKU<%Zu#AOHXW07*qoM6N<$f;-N=*Z=?k literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_14.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_14.png new file mode 100755 index 0000000000000000000000000000000000000000..a66867a32660267596ccc34eb548c63b12d9079d GIT binary patch literal 1929 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 P00000NkvXXu0mjfPmjjh literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_16.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_16.png new file mode 100755 index 0000000000000000000000000000000000000000..2632103d6cb7b296f9858f9084fb7f1ea1991e59 GIT binary patch literal 1850 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 KMNUMnLSTY037nV! literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_18.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_18.png new file mode 100755 index 0000000000000000000000000000000000000000..b5a85de1df6730ba2978509bd7b4f7445632de5d GIT binary patch literal 1715 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 M07*qoM6N<$f`h@c00000 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_2.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_2.png new file mode 100755 index 0000000000000000000000000000000000000000..ca4bf322ba1a693b44b6ddf283a5576467b04d05 GIT binary patch literal 1592 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 Bx1#_6 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_21.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_21.png new file mode 100755 index 0000000000000000000000000000000000000000..6a40d4148f0134314776a5f1ced7826695ac5bf3 GIT binary patch literal 2399 zcmV-l3840gP)U8KeNH?a zTbK>XuRAKt0*Ap?#ya|{dGkJV1OuaNz%^=4<`3?vd_vvMa|LMSb2g5#a`Jhj;vK={ zp<`9R7<#i4r2@w6V)x7@21W(S6YE)wGS*T6b`Ago!5aWv7N}5{QIJ~iUNI0XU@QPC zfKKw%HSW(C$yvv!UV$Ks1L~Ncvz$@}sA?z^Mr#!avf8PF#%x79)f~Lfd|m+~VskQ28!hvfA1`G0*czJ6SLk^}2RsV2nPa{|+-U{mdr?24@|{ zN&+CGN~NrWcsEGZiBYz&y|VF68AAKJdNWb;=5t2z>_EnK+5DSLj+-fmnZn)Ly>nu+ z;ARZaEkhZ=9Di6WD<);JQ+{L&%;fDg5S5^zD?w8MW3rQcXFpZW9K9KsS+aJMuhwN? zVRo{7&HFnVqmY9}tDq^PIss$*D>=acN>2w|%3suto~sOhw*C|YuzekW7#Nv< zl|L(GCwrzoGy8u7V71nD);r^9V6}di0dSuR+!zmLSGKRqWKNtMdn@~IwU`AdtDjM^ zLL9D9Nn_7DG|BX_`}o-jD$aN9$l}rUN3k%ow>lKh^Ze-n+*(%!4Grxeo~>iSJk0=V ztU3&tAIu*Xi%L(H2ltRqnLU+WwB0=ns}c{LTeB;BJ9@?tJwTj{&(UaR1T(gC z#r@SatCdk422kM3l7Mqj*jlG%cgha#)6p}=7 z6Y9i2(-UL)!UrsQ9jDc4+D*1YXPG_)O64EZKlfNkepe6kCj*NDfr`ruasw^@+>UkEU&OQnRDqk<8x{i|-{i=F|0dRyRCO*$}tulb} zr8_jB}(DJhn#$G?xJ~U!0_`fLk3z^lhaat8#Rs z5#3FhGg(Pt^YLp%vUPFHu^AH@pR*GhgNS=kIX{j_9M%;qQtP~w996Ve3%6UNl>p8iIVq6???H^e!;owBrAW6`FQ{zrW)c4f`r&AL|y1UP>+Si7Q5 zG_snSUp29;?0xI>GjYqY(*IThG6jyo-I{b-R1bilX7lkGOY}~{BM|X$1Vu6^-3p#Au6k$lF{mk9su`u?5|FBXWz`8tSmWn&scDd-wG+{oC@xoo1Jvk z7ci=v5>d^ZagrbXP@mC`suVDB!O~^JE@+%y1n0f|bucr)0YSWHwUyP!Ifg-Z8!e~S zWj#RD&HOnnon)>sMs*nCym|fcOQDMX9HsHJGQniK0cWh_UYT$6J_CXp|CX$lsp_z@ z`1~jVagf!~tW2O{#uAFgGab>ft~kkLx)BJSy?24L)1x@O0D|JKf?Fw7Ck#)aL)XmF z6ClWZtJXSTz_Ru!$&w%EsQ5B4(0?d$rPfx4XLOzQ4$<*dsgmOOqQHQ?%&sU~oaD#3 ztXyLruItMGfrt(W*olvyPsl0@WHunfIQm&Hb5hV2H~E-c#Q^d8RsyQppEZKaiAu8G z1E24TN?5evUuXiSEn|4!rJPdHKf9*?XHb>=tZ_Rjs+ru?7bPbBZvz2Ki!0qjI>s2+ z|LgMaSaljf1`>yU<7ad#TeBn8+rcOHbnNNw(~0rh_y7P@3YFrl;aGXKQD7(?GHajP z$SQE22E*1UkalcMN@qqHoq8hvH^9-z^s%hvxNUBj%iZhw^9A|AoX? zrM6r`|5mqMo&0P~_V)7)$v|0wT8+Q`LI}QsWP$3ua#UUFH$_pVyGFet zexfmS^sBcrHjdZp?G<3WmjU%EhLX`uX4K!3su$?^na$s^Pl;<=_Et2;$N{iwOhuL5 zSFukD3r-Z*wr_opptHXdX9@t^aHU7cPSwi$_$Q?T!5E+Yfz+z*iiw=K{s-Xk#uehx RG8_N^002ovPDHLkV1ghXm$m=^ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_22.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_22.png new file mode 100755 index 0000000000000000000000000000000000000000..67189811d89055dff6660f1f2c4286d65e890f68 GIT binary patch literal 2396 zcmV-i38VIjP)Us8+6VEd=o;ySl946lj`PSz3bj}QI|YD};3ENC6sWRJK@dCder87?ham?r z2RgxHpHV(SB6pn4~B8qziE$@b)HW%EojB1CeK z4EZtJUmoR?Kx>R8Qm#2EDExDjB2A*?tEZ5r1S8O9tm$ z_7zP41(l3hC*xfwRTaZ@L9*2IP8-Vlcljz}`|foF@u(opo1-vhFk-mcyH_ek zIaeV-w+*QTbL>IBtdJD>j@h9Q7}3XTU}lmPopBm-7^0QnJMvgNbNH%cM!{MQK0Bw9 z1?h?6bzh%R8J0O&Y34MhRL5aRKBFB7!1#2Mi`kp`E9$fzXMau#X8Rc#BLrZ*P6=}} zzDk1XyaUh3aqJnh%V~v3K1yn2ODC90hAPHv@6Aun0L+n2uX8d!J44Av=ba4C>uOJB zm(n34M8}?)wxibAJ<|U-0cLw$=e+YgE9jg@y$3_1r z$VsYyW0b-Z_+n)kbUD1UVAVJM9Bt=i&sU4Lf)x7RsNL+720N9T|; zyKSdJ0QRgZ24x4br&IAx9NZ#N==FvG%t1;ar`L7hj>+i4Wh8|FXw3~m4t9ku*_QZ; z)-%O`GJPF?#AhcGu6kWIh$+g>cQe0xUg=Zu&AL~6cao}b=EuCJ)BA6NGeF$T;LUZK z8+0X~eX3r`oXZe^C7*I$Md++btcsU|n3Z3$pUmt}hQ9bS(<>`v z$vVf+)#_)3g5;~V<0gUPbJL3{SP4a+xkqJ(A%|vC{g18hM1YJiv%yzL7Wt^6LN>bo zSA3!`UwHu;+<|XaO{>9n;7svTfn;r{nk^WqHX7~e_UvvED!<&%U-3pcuiLwkBblR+ za1Youx9!`>E zKWDzmo|Pm}d|u2cu27_O)yFF8LT;9!63s;bWJ~_+w6(n7K}4qwBfU^NX6@*eh0XMS zq_i9G*1&mcyLlA^@Up zi&2S|bx=_4RD6e661@FFNZ#00qDqP<$U&MIToh`BPSxoZvM+;C490MyjdYae_z=l$?8_X<#P_*dR3%{8gkt{VnRND!vNTjE)E`BcIAY zGp!TJLo*PPd9vUm#<905$2wDK%~qL?g)M)Hwm7YpK|fuKWBCxQ-(S8rtH{m60jg6SVKCZB#57J zuS2szKrJ96(rkgOoQ*tUGQb%i5^^YeMVuV~bC73;9j5<`E{>z!`JqCvcy4#~D_LU< z{uZF98XQ;O0+6ixn(blF(VA;xb;dzWr2A?W?@W|a1uZL&6t9xNp(v9DQ5meNcK1Aj z^qNV`Oe?7A?nQorZvk-=>t|@7nF;<)D{lo#Zk1fSsj_{gdKsETq;cAF)Kww?GA=8E zlHl8k%G?1gFpH-SnClz}^gUkz4BAC&~(Mx`jfJAkS;nfp1SY6eqn0lo!9 zVTyS)w1QSOWHM(|@>$SV5MU-*RCRXw&lFU55Py4qUDw3~I!;Fhqv~UdL}EkEuAynB z|1&+1y}O7Nqob3X1UJ3?QYaJXv)a`r`k)W^6&jFGDe zY!b{6Lab`m*-0}p)GUOak1_B+6xkWP6m|Jz`EHwV9P{LUMIGH8*w2(J+}R&V{sR#+ z!Rwrx3CwCTAaY^1qjQ`UIy$T-pPIoCeY5}vz8yu~;8&CSE)@6{rW*?hunb_otx3MI}5m|7WG2+&a87kf91v4)0|3i6rbt0Pf+z%(ixr@|C?4Sy5f%# zwtDMgdcHaacLURb=%<2?5+d(xG zWS%QjCDTmBSNo0&XdR7LZ9plDcy$c_231MW)om8`)%oe^-n>7?`18MqS4MXNSoJ7} z$G6CFW<@KyyUA8nvWoy8x{se0P#Lk}h~1U8o1DR^IzLNF-fEB?JN^TP4Vbp$QX}mE O00003aGA literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_23.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_23.png new file mode 100755 index 0000000000000000000000000000000000000000..b770f32088c543b70ba617ffa71b3a1bb7a7fb2b GIT binary patch literal 2398 zcmV-k38D6hP)lAW5=Djln9iw!>J!*}!2W2Xokay*|N@!)Xdyl@V#PflScL;-r zhGh<8+3PAwIgIJW?wL+38C6oA*w1|AP)j9nr2tqGyd*#s1+uJj5Ty23ujmNoFy;W| zz)JAcHOgm*RN2R%UL}F^1M--kT}&wgWIdD-gS9LPT<=s)W4fZAY7O2yo0WVQ1cpcz zB#V?KwrioPoiO|68rcHa24ubqm#nvTE9Sm$sV8%Wf?lOZWe>hn6`LU=OLk_HN`}fl z^p#8i2bGFhC*!M5$|^?b!eqJkSzTtOs_2&-Occs-1Pn1;ix* z+gqJI;=XFzb@7__kEqOU?^a569H!*#kgFV&9qnRQBxkSVpYh4~RuHp-kPl_7%h+g? z%Rsds6=F#c4ORQArTN`b3*Q_NfeKS8Y zeVt#J9#6J8F0MG8K@BR~xuSe^b-glD1p$~nE(p=SDpitUrR|{qonO$;PX#NP(25dh zyy|)J8fD{t=PXybtc5tIFrA}}u5M^khKz1)*Z)}pKPw8ZBmr{*20QE5OgHxYBmzvH z4%I3;utHEl$tss6@JbP~M1SG~7Q7WCU{%N*uN+!gTj_Di7R4vNQ-Nm*oax~9Gx%n_ zR@<>k`Y~VzV3Y~P{p^|RN6tYjlqFAw0Iu3um7`4N7iEt^PPC_5d@c!8yyy&&1zR~% z8K@w?6KIA~@wwyGMS!lX3JNL0pfR~|-D*2l_CA$UP^cp`KI>x{Wj)vJV^zG009nOn ze5<6AV6_dicFg#U5q=n?;JRyA1xj%}eyxz($9grs&w?Kb;q?SLpRWWz^1){@#t$Vl zqnI#ke>PX_1RLeB`_39w=@r>0lVA+}eU9q2qHA3L*Dr+N!fY^9&sY253Uyi(-m5ho zI++A4QL`Bgugv!t_;~+N0~&5%En=nmPZJu-5+Xx@D)c*w{y~K6`U26hQk0Gt3;evN zzY(P1!u9uT(ta1Aqp|?MBQeo#_EnY3`nH03R-8;9mjo{}KsO28!14)li53IG)^`TK zjxG!Zc6`G8UhVyA|8EOm{iTuwxV|%xoFs)t3Nlp?rrs+9szN`k{z?+y{;UX9JH{9& zo-Z?inh7d{U)CnxL23rTq*m+aGX~$4gs8N>ZW1_KPx$$z1vq9|ra9$KQVdKaHUqex z-*FhqbP18!+^v4K4-?xVa%S6gU4On6%&xI2s)OH&BZzYGOrxCy2EcH+xk5$z*hG?!+fO z1uz4Z@~drt$-?*O6s3=oQPDvK0q>Qb%I0q3RfCW6I>EoLYh3^Dsm}oTtTJhzM1(4& zs$i?6ltNl0&j6qHT zaUpX+Rxxb<2~wwoI3wWD#dOF};hVIIlx{G`_i^1A1OXhm0N|Qcr(!T;P~8y4DxiW6 zl^*-AB#0t6I%d^Ypc0G)dQv5rEA%f1b0yijQ$PCni9zi$ueOeurBMNt z$DYS$y8~r)-a-8Q2cO%1#TbJa_i5 zBET3wEnq4U4Z-SlRml>^RX>|X6_04(4y{1zys}w@%^fSWo7|B{wf9G#zx;7%S3%VZ zvKb_;OY!FoIRuidPg;mEX{W2I` zJ$-a&Hv!^(XAr#apYH@ZC02spB^)55jEt)56znT!ofr_Sw3}orNHF)Me;m3>*rPiE zeTU-}-%9X5kqliKYL;=qXQzYGABUn~Twk5^T_(OO@Jg3S)$02sB4ku)smw0+{8=$R zGU!mnY2R=vCNZm+c=Et?kxtGADjA*33KHP+xR0I!q7ZlNV@_kFrgvuCr%45WQpdJ; z>ZoM9Qxg09ate4k0*u!-&)XjJdNMOhMz;j8eIR5=QFW}0h^|Nr;!>|EUq9-HRX zar4^=FouWCNT-J5ovYKQd#d?u0hwWyj91Dq9-Z^*r|47qRD)gBYe(<@04_JN`SGUv Q9{>OV07*qoM6N<$f)^Krl>h($ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_24.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_24.png new file mode 100755 index 0000000000000000000000000000000000000000..4a899faf2fa41977236c1259548e27ee6723c612 GIT binary patch literal 2394 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 Z`~w)ArNa<0?~MQe002ovPDHLkV1jFZk7fV> literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_29.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_29.png new file mode 100755 index 0000000000000000000000000000000000000000..f18f8798de868ac7f9153c99fb3d0fbe5b05b157 GIT binary patch literal 1852 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 D0)@Uc literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_30.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_30.png new file mode 100755 index 0000000000000000000000000000000000000000..64cf5858030b039b43e24eb3ed49a26394581828 GIT binary patch literal 1829 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_32.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_32.png new file mode 100755 index 0000000000000000000000000000000000000000..417419f8582eb96a70806fa7b111f5709d768291 GIT binary patch literal 1888 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 QIRF3v07*qoM6N<$f;&2|82|tP literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_34.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_34.png new file mode 100755 index 0000000000000000000000000000000000000000..26dfe53911400e4daf2cd14640154546bac8b442 GIT binary patch literal 2056 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 eNea(j7? literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_36.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_36.png new file mode 100755 index 0000000000000000000000000000000000000000..7bd425e1d105c77f4efcca2ed6d34050bc3305db GIT binary patch literal 2042 zcmV;`Tt+`KFlh`=|e+$;BfJn8*!xLUFIhTrx(?#CE$FUE>`qBZteWS`-!`0e@g;_3Y95Q2yI zF(~i;(@FP0wD%(rtsYi1tiTYLEcpyK4_D^5{f@rc`)L70K}1Hs>FNP&<6|I3`YOzk z`GxzvWJOHo;@q z2Jqrl`DJ}qi_6+(oz9q<$qP4UU}ni#-QnJqf!|>O1hqro_Ont1xW2+L*LD5c z8|gv(Q1=T#L}itv&J>SVu^C-raLoP?rcdcu#y}p>jHa_VD#wghkWt=P{NW6+Dych) zBDz)>zyl?V+tbDLM9iUda8v}SBg&ioZuNZ%;OQjqyhcgF_&so02S@ghQ6f?N$HdLx zVC}xT%KoOSO)^UWCQC_z)$4(5ZL{w0Bp>}%lDKkzHKNB+?!E!;15B|GKWL z1D+?9z&w3BlkO#>(&6lxf=Lu_ghypi;AbT+1)nOrWXBpizQ(=RDsXM} zRmD~QTUo0As|@Wd^9oIMXYEu%OhlKC-1SfZ~hN95Zou#lrz(LcB^0 zfJ!2>f_i4DghwU|U~1GXUCTbQ!TpMQ_DE%1mB7 zoTmRZPzeZ~Q1?^^tgHw;eau#NjCkv-K1Z@^_ClPtF#w|{b(ouxDg&^NuQ15UBf`UO zHOjKaF-&^O+FiiidLRk7Yfu5tQq%y&K zetX4Ji8CsQm5rhtpOrTX>mS-Wjw5G)j;u_++V$A!mA8Oy{9T$yUsTqdoh#1s_g`G4 zzXU!hmJ&Pr*ejeTS^NKN{e&?uzzRn_#vOR2#Em zndBA*Q1#fvSCVf(M@7v1P@15E$sL6dNhR+0am>B^jFqbu<>Su z-NQ5+d>JFY?U&s}V4cGIRq0IiQ9Rk8h-)j@R0qE9uvhiz!vJ`5=M1p=472yT4A7A- zMhZ30G9kNX^$*{F6u>tPf=s%}x9eu?+3&L$&0?z@tDa4Z&)aVL7n48B0PN_p!)lkZ zXN2V^J7N%*w|CPUB%YGUekJ*KeMcfNapHffag_N2@u7bcI21m?23gT5N%wqSb>^zR z@5YQZYS#I7o%?SA-ljVWKxG>&(3Lh;#kuNjXO$gREbcFXxZy4tbQ(RwGuQg7+K8lk zPN+Q&-M(HRrVso|6+jg;2R1dlWYda z_GY4VE52n^J3L~&9eYCt$TsX0R?%6dcNn1Kmr`tKH+qwLs= z>hKx8p>&g8De#PG?mAAp!>_mJg+bt^-BJn0^R+_zOlNoI82j=0-y|=utL~EMyplgLb;znBQ0Z1JfR&w(daI(?**#}f;|>37;a2RleAk?y2%foF(lhX9Un#R0q%)gT zuHKBqd_B*T&j6L6tAc~4r?c8c)ymIou4aPDv9prVFh;BYID#SYF3HX*=^Sf^KjroWkrdSoy!tfWG1m?U1>Sn z_B@Vb9Gfu4IL`B=_up`}u=l3k_B+l;8*wh$igKbc_F1$(^S7e6r_YCn^S46iJiL!a zdH0_Vy5~fDKXRg#!;*$282Tj(KJ%OBSEjf9j=I|WX#zw>M2o)Z>N(ij$DA0+t1w5V z7tZ&B6)~9?2fUBE&igb0lnTb@QF6SQ0lL9M$1<9)%3;A`!HjIOc6oAX0<2bc#R*R` zfM>629|pm5x>~_3aGoET?s$)lv2sHOs06d)#5XViI<7M>7y&$sh zo+GVtm85!-0kUARWU?~RRz}kiDoKDcSK=1-09jByc*%syTpettXGGuVShX^l9Q30d zQ>ox62~dXT2k*O;_mcpf%JJ}2g=ZC(*->EwY3(sGL>g=}K4i z!XUE%WKQ*hVC8yFM#{ExD?wu{Q1K7b|5H(BCoSKZ99}?d@G_@H0Yv|?1YxM?Z}q4o zfrpFbjT$4Ez^~#>BjN#2!f&NtWp$s&}7`vT|$&?v#bu!rX zP*1hOUFQkGi{vtYc<_7_=^NQ^pZ{Wj9Tnv%n;{C$T?`O8l~sS&HhjjOD`Od(s0qBp z0E&?nWR-Rm14NaAPOl_Dv;}y&cw;MmVxL*{AIEWgb_9QnAv-0)l*1J6Uafq#u1CRh zd?ol1m7nKH{e`wiV1CLzd-&`K{%-6}0;{hFg)vlHv-z0}H3_WlIFHHj>})ZB6?PSN zCV`b1t;Ld{5)eDSvu&X5m9c8||JnjpDk{UIys85uR`>s7`_)sKGpC>7KVJ}TG&ZabJ*m|!am zV8`HhZ+x`Q3iEXlB`J;PNFw7IcjeyCbT|&}tc-~oZC@-wyx^;H**c2;kv+S zP=fzP+Gov~{j4%T6in6^bP5A~i4CmR!+7H=Yh#mdKaVlA_W+*q6{9PASU30+)?k#Q zuJ1K`o=&?8L-lnf2{5tPe2D=%U;$V6zG#fPK8|PIvG{NB+6sC#b&HTd(hr@v}P zXB*5Cq=G7Qb}((*&Hzf&r9Ss$MshO!J$qQ=*X@CJB!bE|ikvKI(;i?hSg9UQ=`Jdj zZ|_x30l3~u4`h3SIlm$!YC5fcyWlbaTU^Cc!SD6}Dg&ZEtZ`&xqGYt|vC3WP3n-fm zLCWr|Q;<2ILER3lOl8|s=U41Qo$-EVo4N*WDOTRMBJeI`do{wVa*xTf{jlnXNGEkJ z&X3k#Z7{tx=_z0*x*3c2EmoQhs0O}e$aGq!W9$*&~y6gnq&dug1^uHrX?ns0PQS*yFzJ&oSu5bo3xO)olo=;T# z!kn(6<9{#Mp3d{$Z47V)yb6o#h!`Mx?v3xf8?ib^>vfrgt=EA_~>w5}g)UogL4oeZ8W**d7xpPvbwgWi%1 z5s=1J`c<6dZ4)c>jxu+k8vHrNCThw6ckOAcTB0giIl`*Vtdto8gsh$e16a5RrQRH$lmwp`1pox_M27M zm593iDwS7`gm(SPdyXyx?Dpx-IHkWFdKJ1aPcUGN5tM6Zn_$7875(IU00r2L{`za- z(}1U6XjI@afZDjNCc#|K&V2)|BRqzjcK`ZY!SnOgr-5$uGg%%DE5Y`Xrn0VAC|)r= z0jposV9P%gIM%uiKsiF*kpz)n zvS4+fCRKEFK8_=1fKH}mP!piS0Lq$oGC{YuSOq(phI5oHAX~JG%sH6@J^H`$x?*2v zon3k=+tP9ZtbjQeDV;sf669Y>g6_Nu(|C5A|F2o6+PmpZ#haL5;%a@e`w>2Nkaz9S s%LK~18dq-zSL)T#|2XpC+i~9YKRPRP)vcRH5BdBTMhp9F>1}_;{b(cZMO#shx5mzS=PQ3_^k(`zbvQp2Lg(Rg zG>Sd%1Zy(r%!&57=R_+RrMqNLi9|;Cf&c*p?Jvyj^X$G)5 zSLECMm}FJG@cfKExVFm_k^wR|5a;nQWvsf17#Oy`!we`_*}5CF>?Mfg*w2;ZV>Vg4 zm|U3vicTi!n>nwtzT#xhSrvIKV5$Ukn2;rGbi*Td!!=f}%K)8V?$GmDqjCy5T-nLi zMSes1(G6B-q0Wps>{@0In_n`XY;h;3Xyh-A%o)mY*_-Oow?Za~RB)G0>SklS>>e)( zo+!){@++ftP#AD^lo}rRfu?5pl#Y@?}@WjnbNadzo5$rD5e$S~2 zUO-q9M8Wm^$>g%Kk@X+Sep=xyh>?;LWdN0|1fv3V6Y$33dhaGN8x&1@s3ZZ(%*0LU z@Ad#!l>w#PG*}Q=uy8&4fyrX}qaW4nx1TFXfHJ(eDgECGaCVS8$*`LgUT_f|Jv>HZ zrNGlOVn_^Jmud0bXII$WNs$@R`HF@ySlJ+0xy;E(*>-OwXo>|Y{!{utE6V(q>$Cbu z270|-ud|(;F(^`gS05IP$_=N^Yo5z|<6V}_S}zGu4?F)i>0~_2GmNb{`{qYf{CGUx z>m%0aG9L!RdxrXXYwg*kB%EM?tPvH5tmjuSfOk`@gYBML>6i%y?N0xM6Q&q@_zwyJ%&H`=dsQNXn<;8qXBvr3=JDV@M0Y$BjRlMIE(Fb13R z0%hl_iBm}ewpQ(Dc1i|Vf;@C?L*^la~z!U`}>5fk9t8lUQBR}?q{ zlSyE8_H1D_(~e9JE7$JZDI3iG&U&<+^$X!Ih3;V0^ok)C=;SLHAfrpRC+gbW1xrd6 zbUU^ZAd}U$H>hSa!xao**Wk}=y$Zgyd(BTKL&pE?^4RBZeGl_ZV1}wH!Jh(i@NP0N zJ-y&Z=V$D$^ad(nGO_Y(#qlretO~B^4Y~}#bgMF2*H4OND*Ej96gaxy16XBu4&Q~U z<&4&LdH}n|27G}vS&U{qU?QPR)k|Ri;Q`lpy-VtQfUBDs59pL^W488ofPrNRlhxg> z$Nor_6I@q~3EP`ufQfQ20dx>+vW`P1!GcGtc0SV-PWmL@a|5f7H)- z35NUGI@ZG|`BQNz{YFsbu&FGq$jk(fF+#>}6_6!!EFg*US$U&~{OqmA zn_hVf=rTr^=23Q*H76QOJooEw1JN0G!ct;qpL&I3V0L3*yry%_catGw=M)QD9iRAc zx&iE9IWl`bu2o5_x?wAWjalp_LiUUupnc)L4V*w$jtzVz_zV>WsPq6CTdqh151!p< z-=Ez$)dL``<5VNX<|0HjF(Mb(_76PG1agMetF9T~r)Y>FO)!`Y2P9CYUf{+%=x^8$oBn3w~rrms0I{ z>UFb&iH45Jsq`K0zYF*^c$uE*;&<$_4W0dbSF@*@<*L2%H-ebJJFuL~^oYur_g;_k z6_%5|-oUcMo&L%M|0`kse-^*`T*(TN6B(^%U*)oLCv-pCXUevyJlb`%2e=E>+fxaC z_59tsj%;vNL*0F=TI;@gfQ37fK~b%pvqo3wf343VOXpZ#)E=*tb+HqC_buRVWOe8; z0J}BO8FnAa?Yt4N^Sf^+)xD@UWq@Bvgb3XR&|?|}<#6^EQng{H-0awK9Pk#9*>DyV zMMl(_O@f_z$@E#DJ;uqDw7c*w0l!fDl@6Y6m7qsKTJkzNXUoncc4G8Z0zBHf;`UdJ zQ331*A8+!64gW&vw01w)t-4wfM75#nl!4t>Mk^y@)1AN=+h-qd0i7mhN2%ke-x&64 zSGoDDYma(`$PQLUteiVofi^4jp8P%J+qtA2_ zSg4zL7OD*R^}mEIu%GRXTYv6GhI&!Ys1clK5bas)JfTSmG^1Ar|EeUYuFuLrynBFMoJpgT=ieE8#BLI( z`3gS12~=a&n2J#r*&65VyOIH>u*(2yem%Fq=WF~Ap}zXe+6bt&00000NkvXXu0mjf DQ>_I$ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_39.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_39.png new file mode 100755 index 0000000000000000000000000000000000000000..30b6bd67e1c2e55059beddacd20e0c5d07176733 GIT binary patch literal 2000 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 B)AIlT literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_40.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_40.png new file mode 100755 index 0000000000000000000000000000000000000000..d413cf0ede7649a87df21726f796cabe144cc090 GIT binary patch literal 2127 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 AoB#j- literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_42.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_42.png new file mode 100755 index 0000000000000000000000000000000000000000..baf3c7c79d561f7be0ce0f6ea3c06befebe5d16e GIT binary patch literal 1463 zcmV;o1xWgdP)S}W^h z>HZG52|zLZvn9^Yrgl#Rbkjgv8 zN?1tX-9a?u__}8yfM$XnvmCNJez3sfbgT2Zu{FVV8Ubd*8#|mp7)l4PbG7}sm4C^& z0|6`%GrQd#kbQW^ofUN7f&it+&Vv)x1XY~8S;)wOyvGMB>n;Qsfkph9^N#C}$(f1F zv^tV`sR>{`pH@N}@KAj*tV$iZ%% z|0Vx_`Z9*((ZjepXGQ+XmAo z%lYFtR#_=K9k%?rGl?6VjW!h%v>H`=en*`Jx7S&5(2n@(Is!7%ql`RvCZcom_O*<$ zoB*x0=7}D^1UZ!7S(vfvyUID4<7g_3Kivua=IT6{8*|icy!OA3f2@#>II6vOylham zf&Qd2w(C0X01;)pt0`LWfCkaX?Z}7;DD1GO)n=t6q=6oVd!G?jXOj~WoU<}bjL2OP z54G0frQqzs3kGlTiIP7<-)v&dB*19HWBzfdrr?9c0v`F9Y2o;tcNbh40Dmo{$PTb2 zZAmm*G;O62O`ju`L9RZ7UEKs&?fjF^TZ{}VgFuh$esvuIE1kb%o3w&jb<{l$vu)dD z_p6xzD+?l}*n&JwTPQbL0HZLbquH`EVSX$4#A;Y(CNgvDEkzD?R})3G14znerdU~L z@@Xp$7N|~v>h}RGtc^z2ku%e>-DU@F7I5=iO_0>z2bke}ZMtRczjGB*Rm>eo`F((l z!U|TXkz~%U$<|C%_3sod}U;mS$%o>j9OguPF`8uKUkm{+%4(krnj1VyM=1 zkAtm)P)>&5jj~_(F?|QHIv=Jzuao28j!3U-_5okS2zK8qBjbN$seDk#>wx&+Hv$Rv=3`kXz+k znvKb>wSJd^xvHF&S=3}^ZtIyr+lu3dI`+7f*;228-;p|C1i*;UJ4k{l0$IzQr^Wy) zgTD+?L%T!0eqd@wu}n%)xIQBpx)RbKEvWQOO7ARJD630`&SFL=mT}Q-j{{U^2Df)DV Rzp?-T002ovPDHLkV1jg@$Aka? literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_43.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_43.png new file mode 100755 index 0000000000000000000000000000000000000000..b4cb1179f388fbf1cad5040e10a9dc10728725d4 GIT binary patch literal 1556 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 FV1hu@Gr|A> literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_49.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_49.png new file mode 100755 index 0000000000000000000000000000000000000000..ef68cf86614c97098e4bc877527deead0c8b577a GIT binary patch literal 2132 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_50.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_50.png new file mode 100755 index 0000000000000000000000000000000000000000..a44a566c7b93d66ffd54487a3c8cca7d48ccf05c GIT binary patch literal 2191 zcmV;A2ypj_P)eK@OGHX0y7O6ks7R;8TS5=9aq zq`u7beO=dc9kSKi+<9T6*%UnikSK9 zC`T0+&Szj{O2%-&`xxuGFA3muFh0-7@yQA>1rH0WWWMW1fu+FAa*BB|zmfpEo!u$I zZz=%G*Ub+>U`2O3SOLfCsA9)^YE1N-DnJL!NfAFm0kCir`R>j~5D=g&W~Ll*EX#*I zUp`)J@C4LO@pi%NuE&a@3fEN@Cf^8_OP=UQ5mR+G;)Uz0TwmxcbMgsSR^%#9%#Y=a z#q7v;tXqsFiz~+Nk}IyQI$bHVvKLOSidlhky2II36@Q`tSk#Gp$M+5iaC}E$p6B^C zH}iw_L*FkfB0B4UxUNev|e^y-8!P$DKD499&BXB!) zi0i(l&i+l;2(khI!E!)|ep!*@+N%2};A6aQ{w)4iCm1JKcKvMOur|QLgK2PuTUXdz`x8vuhU^RDk%50k4X;V@};1 z;whrKeqNJex}6@A?Yv^JKcN7)CUz_3Xep1w*pkMsla#TTM;CS zSH;&ML7C@2Iw;751b#B&cFhX)n~*hA04K(5-D++JtT-o| z--$zwAD;oTWKp)Wz`Bak*^8^rQa)o`8G+b3UC^Rm7T>fvS=`4IpaO`!7zs`&$O#2- z)?jP0_bjg}PK@njQ!-*~D*z^mpP33Ez$|E&1T2mn1;_wo&pNSUz7=Q~?~anF20N|* zY`@ADK53^FfaRh{5Z|++qfZAk)>IvmJGF}M+X~ynHhr@B9GE6o2y0aPxX&h5-sprPLrO{wx->l{@8v?<_> zEDQLxy{uOx#6$tI^<5II_q%n>)UrcH2dlqZz=W~NSV49>AAx6yR7GR)#rZMb_s!wkvTMx8p0Dm&t#ev$KAdOT33;Vq7s7HD}qMUGSZb zQvoWg_F6X%77z3D4zRMiIg;)A!Z~U!Rmbt~1UjA0ieJeWW4l^EzOWGP89?P(7IgKTCD9$P&Lq|N z4*2n2$D%Z2Zn&*=ntxs515#%# zcke%akYaHxJGEBylaZ6jeg*y)1*j&YEXDENgfbTI7z5i?fXrv~g9TSguw%{vG6Pwi z0ak4B{{8%SCAah6!SWqb#d2i3Lx|3PzEc+mw65H<_|`c4{|AWUVz9qyYW;W^-8qy+ ziwCsopc@Zuoi$H_?;d#V?*qgD*Zb)7CylM0C9mQ}A1C6<8l-cknu6X{0{i;_Y_dx9 zoi;f?RE3yyYi6oBvb|XuJJU>*K%I%~p9QGkVrRXR2$g?#?W)_SIWTY)2splL69rJ) z%UA(0piE+S%q3O@ES?8WtQp7SI&MeC`4uTDl4ZED^|uuO4G&%?EIV)^!@GVfb9}O* zP7Z1g_`Cku^CJrI6SjyDU-;bd(aj~Q0lO-ho$k?W?H&J^JH{&&04IIA#bi&N2he}F z$U7Zd<+&^W>qQlSCH?6Z<6bed7n|o2VJfrJj%8OT9!4gWQ>34=ZhYCe zj&i(90XqIzTr3}k>#}eEPhcGMZ&HAX;hsKGf@RJ&dJ=q;^A#5=U8d;weSiIX0d6w? zFUI7xLjuR&^gy_?ZN&JpGsATye>SdSrrJ|K*;dT4W8!sca(^Al|L!)z;$VKS>-zrF zz$yvl+$qROQ?i|b*@`VTilL5{~K R10MhY002ovPDHLkV1j_4D?$JO literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_51.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_51.png new file mode 100755 index 0000000000000000000000000000000000000000..ba00473ddd16115e1bfbace6df50d3da336372f8 GIT binary patch literal 2165 zcmV-*2#WWKP)RCt`-T>b9a^fv#GTzu01rj8v zz25b89LIHRvg^8z*Xw2PKjDgo_XckK&g(fwUdyqvpXeQY7VYoyUBRvJoSx2iK%6|j z&q1mC-zHrp5${JyL_e-*T!C?3GV)#CR9+o!{LWbMep&!g5YeVjx~c?Yd@hNRzm9Tb zxOlxHD^fDW1K($?zM>5D8j6i1K(BgsOQ7S zsa~ETwUfN3DF7#CRlb?5YVn{SSD3D|nJ?bo#d@-{$jNWLa(pUgPG(2GF>jc&D1S(D zwYYF^*Xd4~ot?ZmBRzV*lfAVu!d%z&pS6)6=81iu=v& zOR6FP{i-ChWaHWnX-)+?`EmHq4w&C~b%bzHL`jVZESDX>P@yrn# z6@l|Jp53MZ46yTY$+}AcT(VRgSs`R3a$Js>Z0vN4z~R0{0TArCho515e|)eIb|^wd zj(T4uxS|4Cmu6k5_Q;C6JHFoH{QLes(pB);eP`giM1GEA!x?}#h$L**R*L}FMgAj< z?29Em>+*_RM`}}zGb#dw=fo=C-B~*gLEohS_zr(p??>Y$!tacqcjCkS8GAT3t_1nY zjK%dGixO3l!zw;kAd38KU$;J5SKUjkujEfDG+#^qE?}nOXYBRTzct{8<#Lw-RIo++ zW^1cO(;>g3ybkwI0jQZ)0na03#+dlkpk~hi_>RKE@ns!dElxMDogci<*j)F!e6{{- zASX-NAai_F;#7cDQ%P7=fXbc@!wUJT2)gU>ePyaZ-xOfyW@Cd$x)nH1WYu;lfFsa7 ztK#hxCu8Zv7hzTTeN%wWCQklU(pUvJVJZRDB2ZnIT{*eVRE(|&@Ml*%UD8xeJ4Nnd zQh94DK&6w%&r#)l!qoVQI*y-J;#Q#L;AG-YKBq3`kYv;rvO=Mezsr&Ax zQf^PRuG6`l^^P?58|@zzpNdWKavT$X5{Q9D$yPvibU%(26=c+DS+W>wwFt(~t4)+O zOSBH3XI!A~wgPl)mT@+)+q(ZHphTg zJD-VHLEs*%3NS1Gu5&9L%=>eES^iv}NA9BaQUMTb2fXS?7g)t2;xi1LGr%qdRC8Tn z&$?gvs&+Jga8KpqPX@NKX_jy`Gb78oEyL02z|KKS5zKgZn%l4IW0)G3Z~r#1V^KG` z%`1l>eqNC`^-Pf-#aSJ53Nv1_ZsjrAnc1r*mErIGxmoWB9?!Cbqc8Rs?W^9+Iz`3R zCEQ83!j<{1ImU=>MY`Z8<@eX)WyZJgZwC-EWS$T|BUOy{1%XnftAA8oj8{N>VdR_ zzcX`J3ZO#T%(!NJKkr@Fb#)YAr_AlJj6_u&)wPwqE7n!A9pt+rm{x!Y;J3_mHa>S< ze@6_ez|bm%ddJmS>kQxm=}6-)!};4GKl8UcmT`{328zZX=X+NX4F7l?{>#4wD8T6G zQ+D@Wv;MPnst{abbq;{l=5@+9r2tV-cUj~ggB;h@*^c}1)mfxQj4R69ssbo1_#Ang zRseRc4v9OgQdm`-yr#Op>nOZ-)+0%gDy))TiFru@)CQgRJ3ierJ`1dNzp{?Z^0hin z)%{t2%D=NM3h)U-=~4;9qU#iF)hf-g+DsKZZlId)JzZ1xdtM-(U zQL))E$L}lkxtn~Y3_Kp3AG|Nm04~_yG7hSyl2kPuDSZA|DFVjRU57X_JjY@F4u6C;3UZ-i%7ADn?U;*Gs!00000NkvXXu0mjf1RFsb literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_52.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_52.png new file mode 100755 index 0000000000000000000000000000000000000000..8045749acb6d220582409da302be490c77596535 GIT binary patch literal 2205 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_53.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_53.png new file mode 100755 index 0000000000000000000000000000000000000000..18337cd63c7b7021f0f14c0c2f4b510736127926 GIT binary patch literal 2151 zcmV-t2$=VYP)?5CLUs+GI2iGF|F5VT~3eV-!`5h1kkKeOX z>iwsau7Zf~qadOlS2V7`I4)W8UEEY$9d7)dvEuu*03s(Mqi=eu0Hc2{h>^Yyb7Z*q zyh>KYWGWB*p0SSiwg4&}%nKTLA9 zxX`xibjQq2E2z>Z z1gt~FL4A+Z^>2EPgX%XN3vT?iDFxa0uknonFPoNp=ay zf~t~$dR35_vhi#uX^sUt@p1UC4w&EgbP3_2h=Lj=kmKhH#7PG~;^-8ClCk!U0iGxZ z3_$1kL=m`PaFRJWE_g0pO6Ps|Ztq!9MR+@U&ilUar#jF9%|ub@Dh?HZPKT?tsKeNI zrMDug(<^wR{kVQT+41Lje!a)f5Pm;C;~egLk^vYq+EfW)KWC)U6jAp_cq_#}Z~iy2;W$Xx*v7(*GkV=+r|Jxxl3>*@hn&fDtNmXzy(V& zT9xvnHCLR7!xbLys}_O7y@de~m@?Fzdn~I(fH;(7GqdAE)}>ijM)7mS-5p^$0$)B?8+w?zzOSOc8HR(V`f+V%vgl?Us&bcpkmNm1y_U|%gSn5NyBac8SV(@B0OUJmzH0jQbgz%xJv7(WO=?PrixXB@uS zI{@xcBZ;wP9bGL>H?N%@6+h0KV>ZbEv$KXls=#q@X1Y9*9Sl&hp=_$Jhn?f&zYwJH;+^J=WFHm75&HUmTnjtqB2IW-bL#>@D)g5a4r^tye# zRta)tEY2jY+mzKYgu2JqQw-ol&9T+a=ftaEu?<(W4o9_Pqt5M|cS&Qtk^QLnlyA5X zZEt0ON=J4iN_+(e7e-Z2w+ImHN*rCcX337mQ0C(@1E_sW&)p<4dsK=C@pTwL1#L$Y zbQy#by~3JRx$k?P0USWran%msbvH)FUR59~3@}Uou5&9L%=VQp6~S0k8s|Ju{bvHJ zAYFnhz!i;VwxhjS@ORB@H@jJ9#gB}#6)RQrDsl1pgNq%SR@I#aTmh)s%=XWAVASdA z85PoQ>5c;DA3k~CpRu^Zfvh_+uui)8T+$;w9R_4usJk-e5Z6=1GemVh7Q?qY00OKk z$Ne)GzDzn}DPuDAp67UG1NXUmQPjEWa@)O6tqA*Te+r0zRg|xax)AQr8NHAE&2H^o z;YRt1TR++vc`p4FuoGYwWK4YLexNRuEEW2n^;6w_D!R~5#|kPBU7V;J{cZgofDO-d zDAFH)Mlfc(J}cUY0ixMmo!csrRf*W3+MLc`RZQdgr;~gIYDS*ABV_oqyTPuD@W8d} zY^O(WDzJ^isrN6S1~(1e1B1TXD(`rwj&%)58HQnJ^x>;UjU;^QWt1P zTrg(>^Q4|x{neS>Y0?PGG6S%}mCH{gGt@h4rz-feshMwq&zHVax zmg>HaxG~hVRVn6KiLi8767M%N7r%^2@ckAB_zB^z%SO?xV5hpLI~T2W?@TMnXFaSB z<2UtFz#FAAuDbeEGkl{_=-5mTzONL`4E=X}|1}Jdi3_sZTaj*asNzuj`OcJ$x7p^b z_?a>Qf_byJ8RukY5uncQelCQpcq)w4k&pp62yd0A$JNf{yJk|ed-VcXDGJK~?0h#^ zKPgdgqYK@PU)hCgCP^#fq{7^651B?XfO7inGMki+xUq;fcXTf6MEm^g6qmyP4+BI& zeRDBH3=jpO%T}5HyOPg1Gdwe6={Ur{5W?b=B8|L3x(sj}^S?J>-)CvMo3ycxPsZi+ zD166p{QVa~A|}A~$S4|rT9|&bnchi4S5|4i+qqTzB|CQ`TUn9dJ_Tk*Tw*%N z^@-$Xez^Uu0zipq%I%+#nHjx1cr%#alzfNpI12xD>~YN9KJht>&U~ExF0RURm(1AR zF-A~OFFrXhS5`SYoIs;9&s{V2twk{FC!cfnF`g_}mL!#A^&GOZqBHxyp$JqQchNtd di{c;U?LQ598PTka3!DG|002ovPDHLkV1m)|6oLQ% literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_54.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_54.png new file mode 100755 index 0000000000000000000000000000000000000000..10be0ad16a87bae7c178b3715a76c1e5c210e267 GIT binary patch literal 2146 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 Y1HPr&iQ>d1uK)l507*qoM6N<$g2Z$WjQ{`u literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_55.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_55.png new file mode 100755 index 0000000000000000000000000000000000000000..fd8269bb4d5af2a4b30bbbc4213b4cbe43fc41e4 GIT binary patch literal 1952 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 Qp8x;=07*qoM6N<$f_H(Or~m)} literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_6.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_6.png new file mode 100755 index 0000000000000000000000000000000000000000..56f37fbd8943ee285f8451c64269c2045acf4cb7 GIT binary patch literal 1592 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/meta.txt b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/meta.txt new file mode 100755 index 000000000..4eeb91306 --- /dev/null +++ b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/meta.txt @@ -0,0 +1,23 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 20 +Active frames: 38 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 12 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 1 + +Slot: 0 +X: 52 +Y: 46 +Text: Thanks, man! +AlignH: Left +AlignV: Top +StartFrame: 37 +EndFrame: 40 \ No newline at end of file diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 9aaff3c6f..d9488cbba 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -104,7 +104,7 @@ Min butthurt: 0 Max butthurt: 8 Min level: 1 Max level: 3 -Weight: 4 +Weight: 3 Name: L2_Wake_up_128x64 Min butthurt: 0 @@ -139,7 +139,7 @@ Min butthurt: 0 Max butthurt: 8 Min level: 2 Max level: 3 -Weight: 4 +Weight: 3 Name: L3_Furippa3_128x64 Min butthurt: 0 @@ -167,21 +167,28 @@ Min butthurt: 8 Max butthurt: 13 Min level: 1 Max level: 3 -Weight: 4 +Weight: 3 Name: L2_Coding_in_the_shell_128x64 Min butthurt: 0 Max butthurt: 12 Min level: 2 Max level: 3 -Weight: 4 +Weight: 3 Name: L2_Secret_door_128x64 Min butthurt: 0 Max butthurt: 12 Min level: 2 Max level: 3 -Weight: 4 +Weight: 3 + +Name: L3_Freedom_2_dolphins_128x64 +Min butthurt: 0 +Max butthurt: 12 +Min level: 3 +Max level: 3 +Weight: 5 Name: L3_Fireplace_128x64 Min butthurt: 0 From 6f560ef210f2a6546842aa17f0719d9ac3071312 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:13:04 +0300 Subject: [PATCH 24/30] js updates by Willy-JL , oldip --- .../examples/apps/Scripts/badusb_demo.js | 6 + .../js_app/examples/apps/Scripts/keyboard.js | 16 +- .../examples/apps/Scripts/stringutils.js | 19 ++ .../js_app/examples/apps/Scripts/submenu.js | 2 +- applications/system/js_app/js_thread.c | 87 +++++++++- .../system/js_app/modules/js_badusb.c | 79 +++++++-- .../system/js_app/modules/js_blebeacon.c | 25 ++- .../system/js_app/modules/js_keyboard.c | 162 ++++++++++-------- .../js_app/modules/js_subghz/js_subghz.c | 20 +-- .../system/js_app/modules/js_submenu.c | 96 +++++------ 10 files changed, 342 insertions(+), 170 deletions(-) create mode 100644 applications/system/js_app/examples/apps/Scripts/stringutils.js diff --git a/applications/system/js_app/examples/apps/Scripts/badusb_demo.js b/applications/system/js_app/examples/apps/Scripts/badusb_demo.js index bbeedd445..860502023 100644 --- a/applications/system/js_app/examples/apps/Scripts/badusb_demo.js +++ b/applications/system/js_app/examples/apps/Scripts/badusb_demo.js @@ -26,6 +26,12 @@ if (badusb.isConnected()) { badusb.println("Flipper Name: " + flipper.getName()); badusb.println("Battery level: " + to_string(flipper.getBatteryCharge()) + "%"); + // Alt+Numpad method works only on Windows!!! + badusb.altPrintln("This was printed with Alt+Numpad method!"); + + // There's also badusb.print() and badusb.altPrint() + // which don't add the return at the end + notify.success(); } else { print("USB not connected"); diff --git a/applications/system/js_app/examples/apps/Scripts/keyboard.js b/applications/system/js_app/examples/apps/Scripts/keyboard.js index a34607c29..2b01418de 100644 --- a/applications/system/js_app/examples/apps/Scripts/keyboard.js +++ b/applications/system/js_app/examples/apps/Scripts/keyboard.js @@ -4,16 +4,20 @@ keyboard.setHeader("Example Text Input"); // Default text is optional let text = keyboard.text(100, "Default text", true); +// Returns undefined when pressing back print("Got text:", text); keyboard.setHeader("Example Byte Input"); // Default data is optional -let data = keyboard.byte(6, Uint8Array([1, 2, 3, 4, 5, 6])); -data = Uint8Array(data); -let result = "0x"; -for (let i = 0; i < data.byteLength; i++) { - if (data[i] < 0x10) result += "0"; - result += to_hex_string(data[i]); +let result = keyboard.byte(6, Uint8Array([1, 2, 3, 4, 5, 6])); +// Returns undefined when pressing back +if (result !== undefined) { + let data = Uint8Array(result); + result = "0x"; + for (let i = 0; i < data.byteLength; i++) { + if (data[i] < 0x10) result += "0"; + result += to_hex_string(data[i]); + } } print("Got data:", result); \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/stringutils.js b/applications/system/js_app/examples/apps/Scripts/stringutils.js new file mode 100644 index 000000000..51781328d --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/stringutils.js @@ -0,0 +1,19 @@ +let sampleText = "Hello, World!"; + +let lengthOfText = "Length of text: " + to_string(sampleText.length); +print(lengthOfText); + +let start = 7; +let end = 12; +let substringResult = sampleText.slice(start, end); +print(substringResult); + +let searchStr = "World"; +let result2 = to_string(sampleText.indexOf(searchStr)); +print(result2); + +let upperCaseText = "Text in upper case: " + to_upper_case(sampleText); +print(upperCaseText); + +let lowerCaseText = "Text in lower case: " + to_lower_case(sampleText); +print(lowerCaseText); diff --git a/applications/system/js_app/examples/apps/Scripts/submenu.js b/applications/system/js_app/examples/apps/Scripts/submenu.js index 6744ca452..245551309 100644 --- a/applications/system/js_app/examples/apps/Scripts/submenu.js +++ b/applications/system/js_app/examples/apps/Scripts/submenu.js @@ -7,5 +7,5 @@ submenu.addItem("Item 3", 2); submenu.setHeader("Select an option:"); let result = submenu.show(); - +// Returns undefined when pressing back print("Result:", result); diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index 5ca365404..ceaad5e36 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -195,7 +195,7 @@ static void js_require(struct mjs* mjs) { } static void js_global_to_string(struct mjs* mjs) { - double num = mjs_get_int(mjs, mjs_arg(mjs, 0)); + double num = mjs_get_double(mjs, mjs_arg(mjs, 0)); char tmp_str[] = "-2147483648"; itoa(num, tmp_str, 10); mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true); @@ -210,6 +210,88 @@ static void js_global_to_hex_string(struct mjs* mjs) { mjs_return(mjs, ret); } +static void js_parse_int(struct mjs* mjs) { + mjs_val_t arg = mjs_arg(mjs, 0); + if(!mjs_is_string(arg)) { + mjs_return(mjs, mjs_mk_number(mjs, 0)); + return; + } + size_t str_len = 0; + const char* str = mjs_get_string(mjs, &arg, &str_len); + if((str_len == 0) || (str == NULL)) { + mjs_return(mjs, mjs_mk_number(mjs, 0)); + return; + } + + int32_t num = 0; + int32_t sign = 1; + size_t i = 0; + + if(str[0] == '-') { + sign = -1; + i = 1; + } else if(str[0] == '+') { + i = 1; + } + + for(; i < str_len; i++) { + if(str[i] >= '0' && str[i] <= '9') { + num = num * 10 + (str[i] - '0'); + } else { + break; + } + } + num *= sign; + + mjs_return(mjs, mjs_mk_number(mjs, num)); +} + +static void js_to_upper_case(struct mjs* mjs) { + mjs_val_t arg0 = mjs_arg(mjs, 0); + + size_t str_len; + const char* str = NULL; + if(mjs_is_string(arg0)) { + str = mjs_get_string(mjs, &arg0, &str_len); + } + if(!str) { + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + char* upperStr = strdup(str); + for(size_t i = 0; i < str_len; i++) { + upperStr[i] = toupper(upperStr[i]); + } + + mjs_val_t resultStr = mjs_mk_string(mjs, upperStr, ~0, true); + free(upperStr); + mjs_return(mjs, resultStr); +} + +static void js_to_lower_case(struct mjs* mjs) { + mjs_val_t arg0 = mjs_arg(mjs, 0); + + size_t str_len; + const char* str = NULL; + if(mjs_is_string(arg0)) { + str = mjs_get_string(mjs, &arg0, &str_len); + } + if(!str) { + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + char* lowerStr = strdup(str); + for(size_t i = 0; i < str_len; i++) { + lowerStr[i] = tolower(lowerStr[i]); + } + + mjs_val_t resultStr = mjs_mk_string(mjs, lowerStr, ~0, true); + free(lowerStr); + mjs_return(mjs, resultStr); +} + #ifdef JS_DEBUG static void js_dump_write_callback(void* ctx, const char* format, ...) { File* file = ctx; @@ -243,6 +325,9 @@ static int32_t js_thread(void* arg) { mjs_set(mjs, global, "to_hex_string", ~0, MJS_MK_FN(js_global_to_hex_string)); mjs_set(mjs, global, "ffi_address", ~0, MJS_MK_FN(js_ffi_address)); mjs_set(mjs, global, "require", ~0, MJS_MK_FN(js_require)); + mjs_set(mjs, global, "parse_int", ~0, MJS_MK_FN(js_parse_int)); + mjs_set(mjs, global, "to_upper_case", ~0, MJS_MK_FN(js_to_upper_case)); + mjs_set(mjs, global, "to_lower_case", ~0, MJS_MK_FN(js_to_lower_case)); mjs_val_t console_obj = mjs_mk_object(mjs); mjs_set(mjs, console_obj, "log", ~0, MJS_MK_FN(js_console_log)); diff --git a/applications/system/js_app/modules/js_badusb.c b/applications/system/js_app/modules/js_badusb.c index 648632f51..389539be5 100644 --- a/applications/system/js_app/modules/js_badusb.c +++ b/applications/system/js_app/modules/js_badusb.c @@ -52,6 +52,17 @@ static const struct { {"F10", HID_KEYBOARD_F10}, {"F11", HID_KEYBOARD_F11}, {"F12", HID_KEYBOARD_F12}, + + {"NUM0", HID_KEYPAD_0}, + {"NUM1", HID_KEYPAD_1}, + {"NUM2", HID_KEYPAD_2}, + {"NUM3", HID_KEYPAD_3}, + {"NUM4", HID_KEYPAD_4}, + {"NUM5", HID_KEYPAD_5}, + {"NUM6", HID_KEYPAD_6}, + {"NUM7", HID_KEYPAD_7}, + {"NUM8", HID_KEYPAD_8}, + {"NUM9", HID_KEYPAD_9}, }; static void js_badusb_quit_free(JsBadusbInst* badusb) { @@ -130,12 +141,8 @@ static void js_badusb_setup(struct mjs* mjs) { badusb->usb_if_prev = furi_hal_usb_get_config(); - if(!furi_hal_usb_set_config(&usb_hid, badusb->hid_cfg)) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "USB is locked, close companion app first"); - badusb->usb_if_prev = NULL; - mjs_return(mjs, MJS_UNDEFINED); - return; - } + furi_hal_usb_unlock(); + furi_hal_usb_set_config(&usb_hid, badusb->hid_cfg); mjs_return(mjs, MJS_UNDEFINED); } @@ -318,7 +325,35 @@ static void js_badusb_release(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); } -static void badusb_print(struct mjs* mjs, bool ln) { +// Make sure NUMLOCK is enabled for altchar +static void ducky_numlock_on() { + if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) { + furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK); + furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK); + } +} + +// Simulate pressing a character using ALT+Numpad ASCII code +static void ducky_altchar(const char* ascii_code) { + // Hold the ALT key + furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT); + + // Press the corresponding numpad key for each digit of the ASCII code + for(size_t i = 0; ascii_code[i] != '\0'; i++) { + char digitChar[5] = {'N', 'U', 'M', ascii_code[i], '\0'}; // Construct the numpad key name + uint16_t numpad_keycode = get_keycode_by_name(digitChar, strlen(digitChar)); + if(numpad_keycode == HID_KEYBOARD_NONE) { + continue; // Skip if keycode not found + } + furi_hal_hid_kb_press(numpad_keycode); + furi_hal_hid_kb_release(numpad_keycode); + } + + // Release the ALT key + furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT); +} + +static void badusb_print(struct mjs* mjs, bool ln, bool alt) { mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst); furi_assert(badusb); @@ -364,10 +399,20 @@ static void badusb_print(struct mjs* mjs, bool ln) { return; } + if(alt) { + ducky_numlock_on(); + } for(size_t i = 0; i < text_len; i++) { - uint16_t keycode = HID_ASCII_TO_KEY(text_str[i]); - furi_hal_hid_kb_press(keycode); - furi_hal_hid_kb_release(keycode); + if(alt) { + // Convert character to ascii numeric value + char ascii_str[4]; + snprintf(ascii_str, sizeof(ascii_str), "%u", (uint8_t)text_str[i]); + ducky_altchar(ascii_str); + } else { + uint16_t keycode = HID_ASCII_TO_KEY(text_str[i]); + furi_hal_hid_kb_press(keycode); + furi_hal_hid_kb_release(keycode); + } if(delay_val > 0) { bool need_exit = js_delay_with_flags(mjs, delay_val); if(need_exit) { @@ -385,11 +430,19 @@ static void badusb_print(struct mjs* mjs, bool ln) { } static void js_badusb_print(struct mjs* mjs) { - badusb_print(mjs, false); + badusb_print(mjs, false, false); } static void js_badusb_println(struct mjs* mjs) { - badusb_print(mjs, true); + badusb_print(mjs, true, false); +} + +static void js_badusb_alt_print(struct mjs* mjs) { + badusb_print(mjs, false, true); +} + +static void js_badusb_alt_println(struct mjs* mjs) { + badusb_print(mjs, true, true); } static void* js_badusb_create(struct mjs* mjs, mjs_val_t* object) { @@ -404,6 +457,8 @@ static void* js_badusb_create(struct mjs* mjs, mjs_val_t* object) { mjs_set(mjs, badusb_obj, "release", ~0, MJS_MK_FN(js_badusb_release)); mjs_set(mjs, badusb_obj, "print", ~0, MJS_MK_FN(js_badusb_print)); mjs_set(mjs, badusb_obj, "println", ~0, MJS_MK_FN(js_badusb_println)); + mjs_set(mjs, badusb_obj, "altPrint", ~0, MJS_MK_FN(js_badusb_alt_print)); + mjs_set(mjs, badusb_obj, "altPrintln", ~0, MJS_MK_FN(js_badusb_alt_println)); *object = badusb_obj; return badusb; } diff --git a/applications/system/js_app/modules/js_blebeacon.c b/applications/system/js_app/modules/js_blebeacon.c index 4d19accb1..4b661a2cf 100644 --- a/applications/system/js_app/modules/js_blebeacon.c +++ b/applications/system/js_app/modules/js_blebeacon.c @@ -43,16 +43,6 @@ static bool check_arg_count(struct mjs* mjs, size_t count) { return true; } -static bool get_int_arg(struct mjs* mjs, size_t index, uint8_t* value, bool error) { - mjs_val_t int_obj = mjs_arg(mjs, index); - if(!mjs_is_number(int_obj)) { - if(error) ret_bad_args(mjs, "Argument must be a number"); - return false; - } - *value = mjs_get_int(mjs, int_obj); - return true; -} - static void js_blebeacon_is_active(struct mjs* mjs) { JsBlebeaconInst* blebeacon = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; @@ -83,15 +73,24 @@ static void js_blebeacon_set_config(struct mjs* mjs) { } uint8_t power = GapAdvPowerLevel_0dBm; - get_int_arg(mjs, 1, &power, false); + mjs_val_t power_arg = mjs_arg(mjs, 1); + if(mjs_is_number(power_arg)) { + power = mjs_get_int32(mjs, power_arg); + } power = CLAMP(power, GapAdvPowerLevel_6dBm, GapAdvPowerLevel_Neg40dBm); uint8_t intv_min = 50; - get_int_arg(mjs, 2, &intv_min, false); + mjs_val_t intv_min_arg = mjs_arg(mjs, 2); + if(mjs_is_number(intv_min_arg)) { + intv_min = mjs_get_int32(mjs, intv_min_arg); + } intv_min = MAX(intv_min, 20); uint8_t intv_max = 150; - get_int_arg(mjs, 3, &intv_max, false); + mjs_val_t intv_max_arg = mjs_arg(mjs, 3); + if(mjs_is_number(intv_max_arg)) { + intv_max = mjs_get_int32(mjs, intv_max_arg); + } intv_max = MAX(intv_max, intv_min); GapExtraBeaconConfig config = { diff --git a/applications/system/js_app/modules/js_keyboard.c b/applications/system/js_app/modules/js_keyboard.c index 8958dcaf8..6d7ce0e57 100644 --- a/applications/system/js_app/modules/js_keyboard.c +++ b/applications/system/js_app/modules/js_keyboard.c @@ -6,48 +6,18 @@ #define membersof(x) (sizeof(x) / sizeof(x[0])) typedef struct { - char* data; TextInput* text_input; ByteInput* byte_input; ViewDispatcher* view_dispatcher; - uint8_t* byteinput; + char* header; + bool accepted; } JsKeyboardInst; -typedef enum { - JsKeyboardViewTextInput, - JsKeyboardViewByteInput, -} JsKeyboardView; - 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 get_str_arg(struct mjs* mjs, size_t index, const char** value, bool error) { - mjs_val_t str_obj = mjs_arg(mjs, index); - if(!mjs_is_string(str_obj)) { - if(error) ret_bad_args(mjs, "Argument must be a string"); - return false; - } - size_t str_len = 0; - *value = mjs_get_string(mjs, &str_obj, &str_len); - if((str_len == 0) || (*value == NULL)) { - if(error) ret_bad_args(mjs, "Bad string argument"); - return false; - } - return true; -} - -static bool get_int_arg(struct mjs* mjs, size_t index, size_t* value, bool error) { - mjs_val_t int_obj = mjs_arg(mjs, index); - if(!mjs_is_number(int_obj)) { - if(error) ret_bad_args(mjs, "Argument must be a number"); - return false; - } - *value = mjs_get_int(mjs, int_obj); - return true; -} - static JsKeyboardInst* get_this_ctx(struct mjs* mjs) { mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); JsKeyboardInst* storage = mjs_get_ptr(mjs, obj_inst); @@ -55,24 +25,33 @@ static JsKeyboardInst* get_this_ctx(struct mjs* mjs) { return storage; } -void text_input_callback(void* context) { +static void keyboard_callback(void* context) { JsKeyboardInst* keyboard = (JsKeyboardInst*)context; + keyboard->accepted = true; view_dispatcher_stop(keyboard->view_dispatcher); } -void byte_input_callback(void* context) { +static bool keyboard_exit(void* context) { JsKeyboardInst* keyboard = (JsKeyboardInst*)context; + keyboard->accepted = false; view_dispatcher_stop(keyboard->view_dispatcher); + return true; } static void js_keyboard_set_header(struct mjs* mjs) { JsKeyboardInst* keyboard = get_this_ctx(mjs); - const char* header; - if(!get_str_arg(mjs, 0, &header, true)) 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; + } - text_input_set_header_text(keyboard->text_input, header); - byte_input_set_header_text(keyboard->byte_input, header); + if(keyboard->header) { + free(keyboard->header); + } + keyboard->header = strdup(header); mjs_return(mjs, MJS_UNDEFINED); } @@ -80,40 +59,70 @@ static void js_keyboard_set_header(struct mjs* mjs) { static void js_keyboard_text(struct mjs* mjs) { JsKeyboardInst* keyboard = get_this_ctx(mjs); - size_t input_length; - if(!get_int_arg(mjs, 0, &input_length, true)) return; + mjs_val_t input_length_arg = mjs_arg(mjs, 0); + if(!mjs_is_number(input_length_arg)) { + ret_bad_args(mjs, "Input length must be a number"); + return; + } + int32_t input_length = mjs_get_int32(mjs, input_length_arg); char* buffer = malloc(input_length); - const char* default_text = ""; + mjs_val_t default_text_arg = mjs_arg(mjs, 1); + const char* default_text = mjs_get_string(mjs, &default_text_arg, NULL); bool clear_default = false; - if(get_str_arg(mjs, 1, &default_text, false)) { + if(default_text) { strlcpy(buffer, default_text, input_length); mjs_val_t bool_obj = mjs_arg(mjs, 2); clear_default = mjs_get_bool(mjs, bool_obj); } - view_dispatcher_attach_to_gui( - keyboard->view_dispatcher, furi_record_open(RECORD_GUI), ViewDispatcherTypeFullscreen); - furi_record_close(RECORD_GUI); - + if(keyboard->header) { + text_input_set_header_text(keyboard->text_input, keyboard->header); + } text_input_set_result_callback( - keyboard->text_input, text_input_callback, keyboard, buffer, input_length, clear_default); + keyboard->text_input, keyboard_callback, keyboard, buffer, input_length, clear_default); - view_dispatcher_switch_to_view(keyboard->view_dispatcher, JsKeyboardViewTextInput); + text_input_set_minimum_length(keyboard->text_input, 0); + + Gui* gui = furi_record_open(RECORD_GUI); + keyboard->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(keyboard->view_dispatcher); + view_dispatcher_add_view( + keyboard->view_dispatcher, 0, text_input_get_view(keyboard->text_input)); + view_dispatcher_set_event_callback_context(keyboard->view_dispatcher, keyboard); + view_dispatcher_set_navigation_event_callback(keyboard->view_dispatcher, keyboard_exit); + view_dispatcher_attach_to_gui(keyboard->view_dispatcher, gui, ViewDispatcherTypeFullscreen); + view_dispatcher_switch_to_view(keyboard->view_dispatcher, 0); view_dispatcher_run(keyboard->view_dispatcher); - text_input_reset(keyboard->text_input); + view_dispatcher_remove_view(keyboard->view_dispatcher, 0); + view_dispatcher_free(keyboard->view_dispatcher); + keyboard->view_dispatcher = NULL; + furi_record_close(RECORD_GUI); - mjs_return(mjs, mjs_mk_string(mjs, buffer, ~0, true)); + text_input_reset(keyboard->text_input); + if(keyboard->header) { + free(keyboard->header); + keyboard->header = NULL; + } + if(keyboard->accepted) { + mjs_return(mjs, mjs_mk_string(mjs, buffer, ~0, true)); + } else { + mjs_return(mjs, MJS_UNDEFINED); + } free(buffer); } static void js_keyboard_byte(struct mjs* mjs) { JsKeyboardInst* keyboard = get_this_ctx(mjs); - size_t input_length; - if(!get_int_arg(mjs, 0, &input_length, true)) return; + mjs_val_t input_length_arg = mjs_arg(mjs, 0); + if(!mjs_is_number(input_length_arg)) { + ret_bad_args(mjs, "Input length must be a number"); + return; + } + int32_t input_length = mjs_get_int32(mjs, input_length_arg); uint8_t* buffer = malloc(input_length); mjs_val_t default_data_arg = mjs_arg(mjs, 1); @@ -126,21 +135,40 @@ static void js_keyboard_byte(struct mjs* mjs) { memcpy(buffer, (uint8_t*)default_data, MIN((size_t)input_length, default_data_len)); } - view_dispatcher_attach_to_gui( - keyboard->view_dispatcher, furi_record_open(RECORD_GUI), ViewDispatcherTypeFullscreen); - furi_record_close(RECORD_GUI); - + if(keyboard->header) { + byte_input_set_header_text(keyboard->byte_input, keyboard->header); + } byte_input_set_result_callback( - keyboard->byte_input, byte_input_callback, NULL, keyboard, buffer, input_length); + keyboard->byte_input, keyboard_callback, NULL, keyboard, buffer, input_length); - view_dispatcher_switch_to_view(keyboard->view_dispatcher, JsKeyboardViewByteInput); + Gui* gui = furi_record_open(RECORD_GUI); + keyboard->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(keyboard->view_dispatcher); + view_dispatcher_add_view( + keyboard->view_dispatcher, 0, byte_input_get_view(keyboard->byte_input)); + view_dispatcher_set_event_callback_context(keyboard->view_dispatcher, keyboard); + view_dispatcher_set_navigation_event_callback(keyboard->view_dispatcher, keyboard_exit); + view_dispatcher_attach_to_gui(keyboard->view_dispatcher, gui, ViewDispatcherTypeFullscreen); + view_dispatcher_switch_to_view(keyboard->view_dispatcher, 0); view_dispatcher_run(keyboard->view_dispatcher); + view_dispatcher_remove_view(keyboard->view_dispatcher, 0); + view_dispatcher_free(keyboard->view_dispatcher); + keyboard->view_dispatcher = NULL; + furi_record_close(RECORD_GUI); + + if(keyboard->header) { + free(keyboard->header); + keyboard->header = NULL; + } byte_input_set_result_callback(keyboard->byte_input, NULL, NULL, NULL, NULL, 0); byte_input_set_header_text(keyboard->byte_input, ""); - - mjs_return(mjs, mjs_mk_array_buf(mjs, (char*)buffer, input_length)); + if(keyboard->accepted) { + mjs_return(mjs, mjs_mk_array_buf(mjs, (char*)buffer, input_length)); + } else { + mjs_return(mjs, MJS_UNDEFINED); + } free(buffer); } @@ -153,28 +181,14 @@ static void* js_keyboard_create(struct mjs* mjs, mjs_val_t* object) { mjs_set(mjs, keyboard_obj, "byte", ~0, MJS_MK_FN(js_keyboard_byte)); keyboard->byte_input = byte_input_alloc(); keyboard->text_input = text_input_alloc(); - keyboard->view_dispatcher = view_dispatcher_alloc(); - view_dispatcher_enable_queue(keyboard->view_dispatcher); - view_dispatcher_add_view( - keyboard->view_dispatcher, - JsKeyboardViewTextInput, - text_input_get_view(keyboard->text_input)); - view_dispatcher_add_view( - keyboard->view_dispatcher, - JsKeyboardViewByteInput, - byte_input_get_view(keyboard->byte_input)); *object = keyboard_obj; return keyboard; } static void js_keyboard_destroy(void* inst) { JsKeyboardInst* keyboard = inst; - view_dispatcher_remove_view(keyboard->view_dispatcher, JsKeyboardViewByteInput); byte_input_free(keyboard->byte_input); - view_dispatcher_remove_view(keyboard->view_dispatcher, JsKeyboardViewTextInput); text_input_free(keyboard->text_input); - view_dispatcher_free(keyboard->view_dispatcher); - free(keyboard->data); free(keyboard); } diff --git a/applications/system/js_app/modules/js_subghz/js_subghz.c b/applications/system/js_app/modules/js_subghz/js_subghz.c index 913f8c670..f170aa04b 100644 --- a/applications/system/js_app/modules/js_subghz/js_subghz.c +++ b/applications/system/js_app/modules/js_subghz/js_subghz.c @@ -41,17 +41,6 @@ static FuriHalSubGhzPreset js_subghz_get_preset_name(const char* preset_name) { return preset; } -static int32_t get_int_arg(struct mjs* mjs, size_t index, int32_t* value) { - mjs_val_t int_obj = mjs_arg(mjs, index); - if(!mjs_is_number(int_obj)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Argument must be a number"); - mjs_return(mjs, MJS_UNDEFINED); - return false; - } - *value = mjs_get_int(mjs, int_obj); - return true; -} - static void js_subghz_set_rx(struct mjs* mjs) { mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst); @@ -137,8 +126,13 @@ static void js_subghz_set_frequency(struct mjs* mjs) { return; } - int32_t frequency; - if(!get_int_arg(mjs, 0, &frequency)) return; + mjs_val_t frequency_arg = mjs_arg(mjs, 0); + if(!mjs_is_number(frequency_arg)) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Frequency must be a number"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + int32_t frequency = mjs_get_int32(mjs, frequency_arg); if(!subghz_devices_is_frequency_valid(js_subghz->radio_device, frequency)) { mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Invalid frequency"); diff --git a/applications/system/js_app/modules/js_submenu.c b/applications/system/js_app/modules/js_submenu.c index b87f34fa8..1d2f5db55 100644 --- a/applications/system/js_app/modules/js_submenu.c +++ b/applications/system/js_app/modules/js_submenu.c @@ -7,12 +7,9 @@ typedef struct { Submenu* submenu; ViewDispatcher* view_dispatcher; uint32_t result; + bool accepted; } JsSubmenuInst; -typedef enum { - JsSubmenuViewSubmenu, -} JsSubmenuView; - 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* storage = mjs_get_ptr(mjs, obj_inst); @@ -34,47 +31,38 @@ static bool check_arg_count(struct mjs* mjs, size_t count) { return true; } -static bool get_str_arg(struct mjs* mjs, size_t index, const char** value) { - mjs_val_t str_obj = mjs_arg(mjs, index); - if(!mjs_is_string(str_obj)) { - ret_bad_args(mjs, "Argument must be a string"); - return false; - } - size_t str_len = 0; - *value = mjs_get_string(mjs, &str_obj, &str_len); - if((str_len == 0) || (*value == NULL)) { - ret_bad_args(mjs, "Bad string argument"); - return false; - } - return true; -} - -static int32_t get_int_arg(struct mjs* mjs, size_t index, int32_t* value) { - mjs_val_t int_obj = mjs_arg(mjs, index); - if(!mjs_is_number(int_obj)) { - ret_bad_args(mjs, "Argument must be a number"); - return false; - } - *value = mjs_get_int32(mjs, int_obj); - return true; -} - static void submenu_callback(void* context, uint32_t id) { - UNUSED(id); JsSubmenuInst* submenu = context; submenu->result = id; + submenu->accepted = true; view_dispatcher_stop(submenu->view_dispatcher); } +static bool submenu_exit(void* context) { + JsSubmenuInst* submenu = context; + submenu->result = 0; + submenu->accepted = false; + view_dispatcher_stop(submenu->view_dispatcher); + return true; +} + static void js_submenu_add_item(struct mjs* mjs) { JsSubmenuInst* submenu = get_this_ctx(mjs); if(!check_arg_count(mjs, 2)) return; - const char* label; - if(!get_str_arg(mjs, 0, &label)) 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; + } - int32_t id; - if(!get_int_arg(mjs, 1, &id)) 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); @@ -85,8 +73,12 @@ static void js_submenu_set_header(struct mjs* mjs) { JsSubmenuInst* submenu = get_this_ctx(mjs); if(!check_arg_count(mjs, 1)) return; - const char* header; - if(!get_str_arg(mjs, 0, &header)) 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); @@ -96,19 +88,29 @@ static void js_submenu_set_header(struct mjs* mjs) { static void js_submenu_show(struct mjs* mjs) { JsSubmenuInst* submenu = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; - submenu->result = 0; - view_dispatcher_attach_to_gui( - submenu->view_dispatcher, furi_record_open(RECORD_GUI), ViewDispatcherTypeFullscreen); - furi_record_close(RECORD_GUI); - - view_dispatcher_switch_to_view(submenu->view_dispatcher, JsSubmenuViewSubmenu); + Gui* gui = furi_record_open(RECORD_GUI); + submenu->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(submenu->view_dispatcher); + view_dispatcher_add_view(submenu->view_dispatcher, 0, submenu_get_view(submenu->submenu)); + view_dispatcher_set_event_callback_context(submenu->view_dispatcher, submenu); + view_dispatcher_set_navigation_event_callback(submenu->view_dispatcher, submenu_exit); + view_dispatcher_attach_to_gui(submenu->view_dispatcher, gui, ViewDispatcherTypeFullscreen); + view_dispatcher_switch_to_view(submenu->view_dispatcher, 0); view_dispatcher_run(submenu->view_dispatcher); - submenu_reset(submenu->submenu); + view_dispatcher_remove_view(submenu->view_dispatcher, 0); + view_dispatcher_free(submenu->view_dispatcher); + submenu->view_dispatcher = NULL; + furi_record_close(RECORD_GUI); - mjs_return(mjs, mjs_mk_number(mjs, submenu->result)); + 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) { @@ -119,19 +121,13 @@ static void* js_submenu_create(struct mjs* mjs, mjs_val_t* object) { 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(); - submenu->view_dispatcher = view_dispatcher_alloc(); - view_dispatcher_enable_queue(submenu->view_dispatcher); - view_dispatcher_add_view( - submenu->view_dispatcher, JsSubmenuViewSubmenu, submenu_get_view(submenu->submenu)); *object = submenu_obj; return submenu; } static void js_submenu_destroy(void* inst) { JsSubmenuInst* submenu = inst; - view_dispatcher_remove_view(submenu->view_dispatcher, JsSubmenuViewSubmenu); submenu_free(submenu->submenu); - view_dispatcher_free(submenu->view_dispatcher); free(submenu); } From 7c607abfe6d154ea3448ec0d29379498d0e35376 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 20 Mar 2024 14:48:54 +0300 Subject: [PATCH 25/30] fix module load --- applications/system/js_app/modules/js_dialog.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/applications/system/js_app/modules/js_dialog.c b/applications/system/js_app/modules/js_dialog.c index a457d700c..65c27b75c 100644 --- a/applications/system/js_app/modules/js_dialog.c +++ b/applications/system/js_app/modules/js_dialog.c @@ -1,7 +1,6 @@ #include #include "../js_modules.h" #include -#include static bool js_dialog_msg_parse_params(struct mjs* mjs, const char** hdr, const char** msg) { size_t num_args = mjs_nargs(mjs); @@ -167,7 +166,6 @@ static void js_dialog_pick_file(struct mjs* mjs) { DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); const DialogsFileBrowserOptions browser_options = { .extension = extension, - .icon = &I_Apps_10px, .base_path = base_path, }; FuriString* path = furi_string_alloc_set(base_path); From a5bb1ead68520cc0c751f8fd73ef8c205370af98 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 20 Mar 2024 16:44:48 +0200 Subject: [PATCH 26/30] NFC wording fixes (#3512) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * NFC wording fixes * Fix filename Co-authored-by: hedger Co-authored-by: あく --- .../mf_ultralight/mf_ultralight.c | 2 +- .../mf_ultralight/mf_ultralight_render.c | 6 +- .../main/nfc/scenes/nfc_scene_config.h | 3 +- .../main/nfc/scenes/nfc_scene_detect.c | 2 +- ...nfc_scene_mf_classic_keys_warn_duplicate.c | 2 +- .../nfc_scene_mf_classic_mfkey_complete.c | 2 +- .../nfc_scene_mf_classic_update_initial.c | 8 ++- ...ene_mf_classic_update_initial_wrong_card.c | 58 +++++++++++++++++++ .../nfc_scene_mf_classic_write_initial.c | 5 +- ...ene_mf_classic_write_initial_wrong_card.c} | 14 ++--- .../nfc_scene_mf_ultralight_unlock_warn.c | 10 ++-- .../main/nfc/scenes/nfc_scene_slix_unlock.c | 2 +- 12 files changed, 90 insertions(+), 24 deletions(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_wrong_card.c rename applications/main/nfc/scenes/{nfc_scene_mf_classic_wrong_card.c => nfc_scene_mf_classic_write_initial_wrong_card.c} (69%) diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index eb6911df7..c2aaac5be 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -169,7 +169,7 @@ static void nfc_scene_read_setup_view(NfcApp* instance) { popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); popup_set_header(instance->popup, "Unlocking", 97, 15, AlignCenter, AlignTop); popup_set_text( - instance->popup, "Apply card to\nFlipper's back", 97, 27, AlignCenter, AlignTop); + instance->popup, "Hold card next\nto Flipper's back", 94, 27, AlignCenter, AlignTop); } else { popup_set_header(instance->popup, "Don't move", 85, 27, AlignCenter, AlignTop); popup_set_icon(instance->popup, 12, 20, &A_Loading_24); diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c index 1bc508adc..c4ad67ff8 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c @@ -11,7 +11,11 @@ static void nfc_render_mf_ultralight_pages_count(const MfUltralightData* data, F void nfc_render_mf_ultralight_pwd_pack(const MfUltralightData* data, FuriString* str) { bool all_pages = mf_ultralight_is_all_data_read(data); - furi_string_cat_printf(str, "\e#%s pages unlocked!", all_pages ? "All" : "Not all"); + if(all_pages) { + furi_string_cat_printf(str, "\e#All Pages Are Unlocked!"); + } else { + furi_string_cat_printf(str, "\e#Some Pages Are Locked!"); + } MfUltralightConfigPages* config; mf_ultralight_get_config_page(data, &config); diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 94d845fa3..e522cd059 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -45,10 +45,11 @@ ADD_SCENE(nfc, mf_classic_mfkey_nonces_info, MfClassicMfkeyNoncesInfo) ADD_SCENE(nfc, mf_classic_mfkey_complete, MfClassicMfkeyComplete) ADD_SCENE(nfc, mf_classic_update_initial, MfClassicUpdateInitial) ADD_SCENE(nfc, mf_classic_update_initial_success, MfClassicUpdateInitialSuccess) +ADD_SCENE(nfc, mf_classic_update_initial_wrong_card, MfClassicUpdateInitialWrongCard) ADD_SCENE(nfc, mf_classic_write_initial, MfClassicWriteInitial) ADD_SCENE(nfc, mf_classic_write_initial_success, MfClassicWriteInitialSuccess) ADD_SCENE(nfc, mf_classic_write_initial_fail, MfClassicWriteInitialFail) -ADD_SCENE(nfc, mf_classic_wrong_card, MfClassicWrongCard) +ADD_SCENE(nfc, mf_classic_write_initial_wrong_card, MfClassicWriteInitialWrongCard) ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys) ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList) diff --git a/applications/main/nfc/scenes/nfc_scene_detect.c b/applications/main/nfc/scenes/nfc_scene_detect.c index 34c552aba..3ef153657 100644 --- a/applications/main/nfc/scenes/nfc_scene_detect.c +++ b/applications/main/nfc/scenes/nfc_scene_detect.c @@ -19,7 +19,7 @@ void nfc_scene_detect_on_enter(void* context) { popup_reset(instance->popup); popup_set_header(instance->popup, "Reading", 97, 15, AlignCenter, AlignTop); popup_set_text( - instance->popup, "Apply card to\nFlipper's back", 97, 27, AlignCenter, AlignTop); + instance->popup, "Hold card next\nto Flipper's back", 94, 27, AlignCenter, AlignTop); popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c index c3fb92bee..675463ec9 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c @@ -12,7 +12,7 @@ void nfc_scene_mf_classic_keys_warn_duplicate_on_enter(void* context) { // Setup view Popup* popup = instance->popup; popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); - popup_set_header(popup, "Key already exists!", 64, 3, AlignCenter, AlignTop); + popup_set_header(popup, "Key Already Exists!", 64, 3, AlignCenter, AlignTop); popup_set_text( popup, "Please enter a\n" diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c index eb0aa7c3a..d5033789a 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c @@ -14,7 +14,7 @@ void nfc_scene_mf_classic_mfkey_complete_on_enter(void* context) { NfcApp* instance = context; widget_add_string_element( - instance->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Complete!"); + instance->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Completed!"); widget_add_string_multiline_element( instance->widget, 64, diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c index 961afdf53..7c76260b4 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c @@ -61,7 +61,7 @@ static void nfc_scene_mf_classic_update_initial_setup_view(NfcApp* instance) { if(state == NfcSceneMfClassicUpdateInitialStateCardSearch) { popup_set_text( - instance->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter); + instance->popup, "Use the source\ncard only", 128, 32, AlignRight, AlignCenter); popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); } else { popup_set_header(popup, "Updating\nDon't move...", 52, 32, AlignLeft, AlignCenter); @@ -111,14 +111,16 @@ bool nfc_scene_mf_classic_update_initial_on_event(void* context, SceneManagerEve nfc_scene_mf_classic_update_initial_setup_view(instance); consumed = true; } else if(event.event == NfcCustomEventWrongCard) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWrongCard); + scene_manager_next_scene( + instance->scene_manager, NfcSceneMfClassicUpdateInitialWrongCard); consumed = true; } else if(event.event == NfcCustomEventWorkerExit) { if(nfc_save_shadow_file(instance)) { scene_manager_next_scene( instance->scene_manager, NfcSceneMfClassicUpdateInitialSuccess); } else { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWrongCard); + scene_manager_next_scene( + instance->scene_manager, NfcSceneMfClassicUpdateInitialWrongCard); consumed = true; } } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_wrong_card.c new file mode 100644 index 000000000..c2c36c74f --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_wrong_card.c @@ -0,0 +1,58 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_classic_update_initial_wrong_card_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_mf_classic_update_initial_wrong_card_on_enter(void* context) { + NfcApp* instance = context; + Widget* widget = instance->widget; + + notification_message(instance->notifications, &sequence_error); + + widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); + widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Wrong Card!"); + widget_add_string_multiline_element( + widget, + 4, + 17, + AlignLeft, + AlignTop, + FontSecondary, + "Data management\nis only possible\nwith source card"); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + nfc_scene_mf_classic_update_initial_wrong_card_widget_callback, + instance); + + // Setup and start worker + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_classic_update_initial_wrong_card_on_event( + void* context, + SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(instance->scene_manager); + } + } + return consumed; +} + +void nfc_scene_mf_classic_update_initial_wrong_card_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} \ No newline at end of file diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c index da576a276..12e7ba1ec 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c @@ -67,7 +67,7 @@ static void nfc_scene_mf_classic_write_initial_setup_view(NfcApp* instance) { if(state == NfcSceneMfClassicWriteInitialStateCardSearch) { popup_set_header(instance->popup, "Writing", 95, 20, AlignCenter, AlignCenter); popup_set_text( - instance->popup, "Apply the initial\ncard only", 95, 38, AlignCenter, AlignCenter); + instance->popup, "Use the source\ncard only", 95, 38, AlignCenter, AlignCenter); popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); } else { popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); @@ -115,7 +115,8 @@ bool nfc_scene_mf_classic_write_initial_on_event(void* context, SceneManagerEven nfc_scene_mf_classic_write_initial_setup_view(instance); consumed = true; } else if(event.event == NfcCustomEventWrongCard) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWrongCard); + scene_manager_next_scene( + instance->scene_manager, NfcSceneMfClassicWriteInitialWrongCard); consumed = true; } else if(event.event == NfcCustomEventPollerSuccess) { scene_manager_next_scene( diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_wrong_card.c similarity index 69% rename from applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c rename to applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_wrong_card.c index a879985bc..3f92ebfd3 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_wrong_card.c @@ -1,6 +1,6 @@ #include "../nfc_app_i.h" -void nfc_scene_mf_classic_wrong_card_widget_callback( +void nfc_scene_mf_classic_write_initial_wrong_card_widget_callback( GuiButtonType result, InputType type, void* context) { @@ -10,7 +10,7 @@ void nfc_scene_mf_classic_wrong_card_widget_callback( } } -void nfc_scene_mf_classic_wrong_card_on_enter(void* context) { +void nfc_scene_mf_classic_write_initial_wrong_card_on_enter(void* context) { NfcApp* instance = context; Widget* widget = instance->widget; @@ -18,7 +18,7 @@ void nfc_scene_mf_classic_wrong_card_on_enter(void* context) { widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); widget_add_string_element( - widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); + widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Use The Source Card!"); widget_add_string_multiline_element( widget, 4, @@ -26,19 +26,19 @@ void nfc_scene_mf_classic_wrong_card_on_enter(void* context) { AlignLeft, AlignTop, FontSecondary, - "Data management\nis only possible\nwith initial card"); + "Go to NFC Magic\napp if you want to\nwrite blanks"); widget_add_button_element( widget, GuiButtonTypeLeft, "Retry", - nfc_scene_mf_classic_wrong_card_widget_callback, + nfc_scene_mf_classic_write_initial_wrong_card_widget_callback, instance); // Setup and start worker view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); } -bool nfc_scene_mf_classic_wrong_card_on_event(void* context, SceneManagerEvent event) { +bool nfc_scene_mf_classic_write_initial_wrong_card_on_event(void* context, SceneManagerEvent event) { NfcApp* instance = context; bool consumed = false; @@ -50,7 +50,7 @@ bool nfc_scene_mf_classic_wrong_card_on_event(void* context, SceneManagerEvent e return consumed; } -void nfc_scene_mf_classic_wrong_card_on_exit(void* context) { +void nfc_scene_mf_classic_write_initial_wrong_card_on_exit(void* context) { NfcApp* instance = context; widget_reset(instance->widget); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c index db5fd945f..4df8a6289 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c @@ -22,14 +22,14 @@ void nfc_scene_mf_ultralight_unlock_warn_on_enter(void* context) { for(size_t i = 0; i < sizeof(nfc->mf_ul_auth->password.data); i++) { furi_string_cat_printf(password_str, "%02X ", nfc->mf_ul_auth->password.data[i]); } - furi_string_cat_str(password_str, "?\nCaution, a wrong password\ncan block the card!"); + furi_string_cat_str(password_str, "\nWarning: incorrect password\nwill block the card!"); nfc_text_store_set(nfc, furi_string_get_cstr(password_str)); furi_string_free(password_str); - const char* message = (type == MfUltralightAuthTypeReader) ? "Password captured!" : - "Risky function!"; + const char* message = (type == MfUltralightAuthTypeReader) ? "Password Captured!" : + "Risky Action!"; dialog_ex_set_header(dialog_ex, message, 64, 0, AlignCenter, AlignTop); - dialog_ex_set_text(dialog_ex, nfc->text_store, 64, 12, AlignCenter, AlignTop); + dialog_ex_set_text(dialog_ex, nfc->text_store, 64, 10, AlignCenter, AlignTop); dialog_ex_set_left_button_text(dialog_ex, "Cancel"); dialog_ex_set_right_button_text(dialog_ex, "Continue"); @@ -37,7 +37,7 @@ void nfc_scene_mf_ultralight_unlock_warn_on_enter(void* context) { notification_message(nfc->notifications, &sequence_set_green_255); } } else { - dialog_ex_set_header(dialog_ex, "Risky function!", 64, 4, AlignCenter, AlignTop); + dialog_ex_set_header(dialog_ex, "Risky action!", 64, 4, AlignCenter, AlignTop); dialog_ex_set_text( dialog_ex, "Wrong password\ncan block your\ncard.", 4, 18, AlignLeft, AlignTop); dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); diff --git a/applications/main/nfc/scenes/nfc_scene_slix_unlock.c b/applications/main/nfc/scenes/nfc_scene_slix_unlock.c index b01876e06..ae725ce67 100644 --- a/applications/main/nfc/scenes/nfc_scene_slix_unlock.c +++ b/applications/main/nfc/scenes/nfc_scene_slix_unlock.c @@ -32,7 +32,7 @@ void nfc_scene_slix_unlock_on_enter(void* context) { popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); popup_set_header(instance->popup, "Unlocking", 97, 15, AlignCenter, AlignTop); popup_set_text( - instance->popup, "Apply card to\nFlipper's back", 97, 27, AlignCenter, AlignTop); + instance->popup, "Hold card next\nto Flipper's back", 94, 27, AlignCenter, AlignTop); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolSlix); From 4a673397b3ffd983ca76bd215c85ba5a222fe6c4 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 20 Mar 2024 17:08:35 +0200 Subject: [PATCH 27/30] [FL-3760] NFC Parsers cosmetic fixes (#3511) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * NFC Parsers cosmetic fixes * Clarify the unknown digit indication Co-authored-by: あく --- applications/main/nfc/plugins/supported_cards/myki.c | 2 +- applications/main/nfc/plugins/supported_cards/opal.c | 2 +- applications/main/nfc/plugins/supported_cards/plantain.c | 2 +- applications/main/nfc/plugins/supported_cards/two_cities.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/myki.c b/applications/main/nfc/plugins/supported_cards/myki.c index 4da5d30af..b023a913a 100644 --- a/applications/main/nfc/plugins/supported_cards/myki.c +++ b/applications/main/nfc/plugins/supported_cards/myki.c @@ -73,7 +73,7 @@ static bool myki_parse(const NfcDevice* device, FuriString* parsed_data) { // Stored card number doesn't include check digit card_number += myki_calculate_luhn(card_number); - furi_string_set(parsed_data, "\e#myki\n"); + furi_string_set(parsed_data, "\e#myki\nNo.: "); // Stylise card number according to the physical card char card_string[20]; diff --git a/applications/main/nfc/plugins/supported_cards/opal.c b/applications/main/nfc/plugins/supported_cards/opal.c index d83e908f1..60db7ed1e 100644 --- a/applications/main/nfc/plugins/supported_cards/opal.c +++ b/applications/main/nfc/plugins/supported_cards/opal.c @@ -170,7 +170,7 @@ static bool opal_parse(const NfcDevice* device, FuriString* parsed_data) { furi_string_printf( parsed_data, - "\e#Opal: $%s%ld.%02hu\n3085 22%02hhu %04hu %03hu%01hhu\n%s, %s\n", + "\e#Opal: $%s%ld.%02hu\nNo.: 3085 22%02hhu %04hu %03hu%01hhu\n%s, %s\n", sign, balance_dollars, balance_cents, diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index ebdc902e8..bba31b559 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -191,7 +191,7 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { } furi_string_printf( - parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance); + parsed_data, "\e#Plantain\nNo.: %llu?\nBalance:%lu\n", card_number, balance); parsed = true; } while(false); diff --git a/applications/main/nfc/plugins/supported_cards/two_cities.c b/applications/main/nfc/plugins/supported_cards/two_cities.c index c957a9c43..81b45de6b 100644 --- a/applications/main/nfc/plugins/supported_cards/two_cities.c +++ b/applications/main/nfc/plugins/supported_cards/two_cities.c @@ -157,7 +157,7 @@ static bool two_cities_parse(const NfcDevice* device, FuriString* parsed_data) { furi_string_printf( parsed_data, - "\e#Troika+Plantain\nPN: %llu-\nPB: %lu rur.\nTN: %lu\nTB: %u rur.\n", + "\e#Troika+Plantain\nPN: %llu?\nPB: %lu rur.\nTN: %lu\nTB: %u rur.\n", card_number, balance, troika_number, From 8c0ba4de9fae66c9f922e12c975832055823ecba Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 20 Mar 2024 20:17:16 +0300 Subject: [PATCH 28/30] add icon --- applications/system/js_app/modules/js_dialog.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/applications/system/js_app/modules/js_dialog.c b/applications/system/js_app/modules/js_dialog.c index 65c27b75c..be2b5a95a 100644 --- a/applications/system/js_app/modules/js_dialog.c +++ b/applications/system/js_app/modules/js_dialog.c @@ -2,6 +2,18 @@ #include "../js_modules.h" #include +// File icon +#include +static const uint8_t _I_file_10px_0[] = { + 0x00, 0x7f, 0x00, 0xa1, 0x00, 0x2d, 0x01, 0xe1, 0x01, 0x0d, 0x01, + 0x01, 0x01, 0x7d, 0x01, 0x01, 0x01, 0x01, 0x01, 0xff, 0x01, +}; +static const uint8_t* const _I_file_10px[] = {_I_file_10px_0}; + +static const Icon I_file_10px = + {.width = 10, .height = 10, .frame_count = 1, .frame_rate = 0, .frames = _I_file_10px}; +// File icon end + static bool js_dialog_msg_parse_params(struct mjs* mjs, const char** hdr, const char** msg) { size_t num_args = mjs_nargs(mjs); if(num_args != 2) { @@ -166,6 +178,7 @@ static void js_dialog_pick_file(struct mjs* mjs) { DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); const DialogsFileBrowserOptions browser_options = { .extension = extension, + .icon = &I_file_10px, .base_path = base_path, }; FuriString* path = furi_string_alloc_set(base_path); From 3b83d8b8253b947d07b66993dc659877315d839a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 20 Mar 2024 21:47:01 +0300 Subject: [PATCH 29/30] the biggest update ever --- lib/toolbox/name_generator.c | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/lib/toolbox/name_generator.c b/lib/toolbox/name_generator.c index c0decd10d..e8b8c586f 100644 --- a/lib/toolbox/name_generator.c +++ b/lib/toolbox/name_generator.c @@ -8,30 +8,15 @@ #include const char* const name_generator_left[] = { - "super", - "big", - "little", - "liquid", - "unknown", - "cheeky", - "tricky", - "sneaky", - "quick", - "quantum", - "kurwa", - "great", - "smart", - "mini", - "ultra", - "small", - "random", - "strange", + "super", "big", "little", "liquid", "unknown", "cheeky", "tricky", + "sneaky", "silly", "oh_my", "quick", "oh_no", "quantum", "kurwa", + "great", "smart", "mini", "ultra", "small", "random", "strange", }; const char* const name_generator_right[] = { - "maslina", "sus", "anomalija", "artefact", "bobr", "chomik", "sidorovich", - "stalker", "kit", "habar", "jezyk", "borov", "juzyk", "konserva", - "aptechka", "door", "zalaz", "breeky", "pingwin", "kot", + "maslina", "sus", "anomalija", "artefact", "bobr", "chomik", "sidorovich", + "stalker", "kit", "habar", "jezyk", "borov", "juzyk", "konserva", + "aptechka", "door", "zalaz", "breeky", "bunker", "pingwin", "kot", }; void name_generator_make_auto_datetime( From 139cc3c93d99a6acf5fb859b49d32204234f641b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 20 Mar 2024 22:02:13 +0300 Subject: [PATCH 30/30] upd changelog --- CHANGELOG.md | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebde2dc4e..611a00031 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1,31 @@ ## New changes -* NFC: EMV Fixes and imporvements (by @wosk & @Leptopt1los | PR #702) -* NFC: Parsers refactoring (by @Leptopt1los) -* NFC: Kazan parser improved - token parse option added (by @Leptopt1los) +* NFC: **EMV Fixes and imporvements** (old saved files may be not compatible now) (by @wosk & @Leptopt1los | PR #702) +* NFC: **Parsers refactoring** (by @Leptopt1los) +* NFC: **Kazan parser improved** - token parse option added (by @Leptopt1los) * NFC: Update ndef parser, mf classic dict changes (by @Willy-JL) +* RFID: Test swap of em4100 t5577 blocks (details in issue 3463 OFW) * Infrared: Update universal remote assets (by @amec0e | PR #718 #719) * SubGHz: Add 430.50 mhz (by @MizumasuShoichi | PR #721) -* SubGHz: Magellan Event Code Update (by @wooferguy | PR #713) +* SubGHz: **Magellan Event Code Update** (by @wooferguy | PR #713) * SubGHz: Reduce subghz add manually scene flash size (by @Willy-JL) * SubGHz: Fix led blink on decode raw > signal info (by @Willy-JL) * HID App: apply fix for ms teams on macos (by @cpressland) * HID App: merge official fw hid app keyboard changes +* Misc: Use non prefixed names for regular files with random name +* Misc: Revert usb cdc config changes to verify issue (storage timeout during firmware update) (OFW 3452) * Expansion `is_connected` API to check for VGM (by @HaxSam) -* New JavaScript Modules `UsbDisk`,`badusb.quit()`,`SubGHz`,`Submenu`,`BleBeacon`,`Keyboard`,`Math` (by @Willy-JL, @Spooks4576, @Sil333033) +* New JavaScript Modules `UsbDisk`,`badusb.quit() + altstring`,`SubGHz`,`Submenu`,`BleBeacon`,`Keyboard`,`Math`,`GPIO` (by @Willy-JL, @Spooks4576, @Sil333033, @oldip) +* Apps: Added **FindMy Flipper** app +* Apps: NFC Magic - **Gen4 improvements, Gen2 writing support** * Apps: **Check out Apps updates by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* OFW PR 3504: NFC: **Slix privacy password reveal and Desfire detect fix** (by gornekich) +* OFW: **Infrared fixes and more** +* OFW: NFC Parsers cosmetic fixes +* OFW: NFC wording fixes +* OFW: **Fix iButton emulation regression** +* OFW: Add the Freedom_2_dolphins animation +* OFW: Infrared: Add Fujitsu ASYG24KMTB +* OFW: Asynchronous Infrared remote manipulation * OFW: Fix troika 4K keys * OFW: Archive: Fix item focus after aborting the Delete operation * OFW: Troyka parser improvements (by UL Team) @@ -23,7 +36,7 @@ * OFW: FBT/uFBT: Enable C++20/GNU23 in VSCode IntelliSense * OFW: Toolchain fixes * OFW: Quote $FBT_TOOLCHAIN_PATH to avoid splitting -* OFW: ble: profile rework +* OFW: **ble: profile rework** * OFW: lfrfid/em4100: added support for different bit rates (16clk was added back into UL, still not reading properly) * OFW: T5577 lib: write with mask function added * OFW: Archive: fixed Apps tab ext filter @@ -32,7 +45,7 @@ * OFW: Gui text box: fix formatted string memory reservation * OFW: JS debug disabled, archive and file browser fixes * OFW: VSCode integration fixes for new toolchain -* OFW: FIX ISO15693 emulation +* OFW: **FIX ISO15693 emulation** * OFW: JS serial module renamed, uart channel selection * OFW: mjs: minor fixes * OFW: **JavaScript runner** @@ -46,12 +59,12 @@ * OFW: NFC: Custom UID entry when adding manually * OFW: Added NFC plugin; Some parser * OFW: **Slix disable privacy** (Unlock SLIX-L) -* OFW: NFC: Add support for Gallagher access control (MIFARE Classic only) +* OFW: NFC: **Add support for Gallagher access control** (MIFARE Classic only) * OFW: furi/core/timer: resolve timer handle use-after-free post deletion * OFW: FuriHal: various GPIO improvements * OFW: GUI: canvas commit callback has been moved to canvas. Direct Draw apps can now be streamed via RPC. * OFW: nfc app: fix incorrect protocol detection in save scene (by UL Team) -* OFW: NFC: MFC Unlock with Dictionary +* OFW: NFC: **MFC Unlock with Dictionary** * OFW: ITSO Parser (UK) * OFW: NFC: fix application opening from browser * OFW: Rework more info scene for Ultralight cards