Merge branch 'release-candidate' into release
This commit is contained in:
		
						commit
						94bb1ad354
					
				
							
								
								
									
										4
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @ -15,7 +15,7 @@ env: | |||||||
| 
 | 
 | ||||||
| jobs: | jobs: | ||||||
|   main: |   main: | ||||||
|     runs-on: [self-hosted,Office] |     runs-on: [self-hosted,FlipperZero] | ||||||
|     steps: |     steps: | ||||||
|       - name: 'Cleanup workspace' |       - name: 'Cleanup workspace' | ||||||
|         uses: AutoModality/action-clean@v1 |         uses: AutoModality/action-clean@v1 | ||||||
| @ -166,7 +166,7 @@ jobs: | |||||||
| 
 | 
 | ||||||
|   compact: |   compact: | ||||||
|     if: ${{ !startsWith(github.ref, 'refs/tags') }} |     if: ${{ !startsWith(github.ref, 'refs/tags') }} | ||||||
|     runs-on: [self-hosted,koteeq] |     runs-on: [self-hosted,FlipperZero] | ||||||
|     steps: |     steps: | ||||||
|       - name: 'Cleanup workspace' |       - name: 'Cleanup workspace' | ||||||
|         uses: AutoModality/action-clean@v1 |         uses: AutoModality/action-clean@v1 | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								.github/workflows/lint_c.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/lint_c.yml
									
									
									
									
										vendored
									
									
								
							| @ -14,7 +14,7 @@ env: | |||||||
| 
 | 
 | ||||||
| jobs: | jobs: | ||||||
|   lint_c_cpp: |   lint_c_cpp: | ||||||
|     runs-on: [self-hosted,Office] |     runs-on: [self-hosted,FlipperZero] | ||||||
|     steps: |     steps: | ||||||
|       - name: 'Cleanup workspace' |       - name: 'Cleanup workspace' | ||||||
|         uses: AutoModality/action-clean@v1 |         uses: AutoModality/action-clean@v1 | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								.github/workflows/reindex.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/reindex.yml
									
									
									
									
										vendored
									
									
								
							| @ -7,7 +7,7 @@ on: | |||||||
| jobs: | jobs: | ||||||
|   reindex: |   reindex: | ||||||
|     name: 'Reindex updates' |     name: 'Reindex updates' | ||||||
|     runs-on: [self-hosted,Office] |     runs-on: [self-hosted,FlipperZero] | ||||||
|     steps: |     steps: | ||||||
|       - name: Trigger reindex |       - name: Trigger reindex | ||||||
|         uses: wei/curl@master |         uses: wei/curl@master | ||||||
|  | |||||||
| @ -7,6 +7,13 @@ | |||||||
| Welcome to [Flipper Zero](https://flipperzero.one/)'s Firmware repo! | Welcome to [Flipper Zero](https://flipperzero.one/)'s Firmware repo! | ||||||
| Our goal is to create nice and clean code with good documentation, to make it a pleasure for everyone to work with. | Our goal is to create nice and clean code with good documentation, to make it a pleasure for everyone to work with. | ||||||
| 
 | 
 | ||||||
|  | # Clone the Repository | ||||||
|  | 
 | ||||||
|  | You should clone with  | ||||||
|  | ```shell | ||||||
|  | $ git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| # Update firmware | # Update firmware | ||||||
| 
 | 
 | ||||||
| [Get Latest Firmware from Update Server](https://update.flipperzero.one/) | [Get Latest Firmware from Update Server](https://update.flipperzero.one/) | ||||||
| @ -74,6 +81,8 @@ Check `dist/` for build outputs. | |||||||
| 
 | 
 | ||||||
| Use **`flipper-z-{target}-full-{suffix}.dfu`** to flash your device. | Use **`flipper-z-{target}-full-{suffix}.dfu`** to flash your device. | ||||||
| 
 | 
 | ||||||
|  | If compilation fails, make sure all submodules are all initialized. Either clone with `--recursive` or use `git submodule update --init --recursive`. | ||||||
|  | 
 | ||||||
| # Build on Linux/macOS | # Build on Linux/macOS | ||||||
| 
 | 
 | ||||||
| ## macOS Prerequisites | ## macOS Prerequisites | ||||||
|  | |||||||
| @ -44,6 +44,7 @@ extern int32_t vibro_test_app(void* p); | |||||||
| extern int32_t bt_hid_app(void* p); | extern int32_t bt_hid_app(void* p); | ||||||
| extern int32_t battery_test_app(void* p); | extern int32_t battery_test_app(void* p); | ||||||
| extern int32_t text_box_test_app(void* p); | extern int32_t text_box_test_app(void* p); | ||||||
|  | extern int32_t file_browser_app(void* p); | ||||||
| 
 | 
 | ||||||
| // Plugins
 | // Plugins
 | ||||||
| extern int32_t music_player_app(void* p); | extern int32_t music_player_app(void* p); | ||||||
| @ -419,14 +420,6 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = { | |||||||
|      .flags = FlipperApplicationFlagDefault}, |      .flags = FlipperApplicationFlagDefault}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef APP_SCENED |  | ||||||
|     {.app = scened_app, |  | ||||||
|      .name = "Templated Scene", |  | ||||||
|      .stack_size = 1024, |  | ||||||
|      .icon = NULL, |  | ||||||
|      .flags = FlipperApplicationFlagDefault}, |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| #ifdef APP_LF_RFID | #ifdef APP_LF_RFID | ||||||
|     {.app = lfrfid_debug_app, |     {.app = lfrfid_debug_app, | ||||||
|      .name = "LF-RFID Debug", |      .name = "LF-RFID Debug", | ||||||
| @ -459,6 +452,14 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = { | |||||||
|      .flags = FlipperApplicationFlagDefault}, |      .flags = FlipperApplicationFlagDefault}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|  | #ifdef APP_FILE_BROWSER_TEST | ||||||
|  |     {.app = file_browser_app, | ||||||
|  |      .name = "File Browser test", | ||||||
|  |      .stack_size = 2048, | ||||||
|  |      .icon = &A_BadUsb_14, | ||||||
|  |      .flags = FlipperApplicationFlagDefault}, | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| #ifdef APP_BATTERY_TEST | #ifdef APP_BATTERY_TEST | ||||||
|     {.app = battery_test_app, |     {.app = battery_test_app, | ||||||
|      .name = "Battery Test", |      .name = "Battery Test", | ||||||
|  | |||||||
| @ -62,6 +62,7 @@ APP_USB_MOUSE = 1 | |||||||
| APP_BAD_USB = 1 | APP_BAD_USB = 1 | ||||||
| APP_U2F = 1 | APP_U2F = 1 | ||||||
| APP_UART_ECHO = 1 | APP_UART_ECHO = 1 | ||||||
|  | APP_FILE_BROWSER_TEST = 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -207,6 +208,11 @@ CFLAGS		+= -DAPP_KEYPAD_TEST | |||||||
| SRV_GUI		= 1 | SRV_GUI		= 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
|  | APP_FILE_BROWSER_TEST ?= 0 | ||||||
|  | ifeq ($(APP_FILE_BROWSER_TEST), 1) | ||||||
|  | CFLAGS		+= -DAPP_FILE_BROWSER_TEST | ||||||
|  | SRV_GUI = 1 | ||||||
|  | endif | ||||||
| 
 | 
 | ||||||
| APP_ACCESSOR ?= 0 | APP_ACCESSOR ?= 0 | ||||||
| ifeq ($(APP_ACCESSOR), 1) | ifeq ($(APP_ACCESSOR), 1) | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "bad_usb_app_i.h" | #include "bad_usb_app_i.h" | ||||||
|  | #include "m-string.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| @ -22,33 +23,13 @@ static void bad_usb_app_tick_event_callback(void* context) { | |||||||
|     scene_manager_handle_tick_event(app->scene_manager); |     scene_manager_handle_tick_event(app->scene_manager); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool bad_usb_check_assets() { |  | ||||||
|     Storage* fs_api = furi_record_open("storage"); |  | ||||||
| 
 |  | ||||||
|     File* dir = storage_file_alloc(fs_api); |  | ||||||
|     bool ret = false; |  | ||||||
| 
 |  | ||||||
|     if(storage_dir_open(dir, BAD_USB_APP_PATH_FOLDER)) { |  | ||||||
|         ret = true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     storage_dir_close(dir); |  | ||||||
|     storage_file_free(dir); |  | ||||||
| 
 |  | ||||||
|     furi_record_close("storage"); |  | ||||||
| 
 |  | ||||||
|     return ret; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| BadUsbApp* bad_usb_app_alloc(char* arg) { | BadUsbApp* bad_usb_app_alloc(char* arg) { | ||||||
|     BadUsbApp* app = malloc(sizeof(BadUsbApp)); |     BadUsbApp* app = malloc(sizeof(BadUsbApp)); | ||||||
| 
 | 
 | ||||||
|  |     string_init(app->file_path); | ||||||
|  | 
 | ||||||
|     if(arg != NULL) { |     if(arg != NULL) { | ||||||
|         string_t filename; |         string_set_str(app->file_path, arg); | ||||||
|         string_init(filename); |  | ||||||
|         path_extract_filename_no_ext(arg, filename); |  | ||||||
|         strncpy(app->file_name, string_get_cstr(filename), BAD_USB_FILE_NAME_LEN); |  | ||||||
|         string_clear(filename); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     app->gui = furi_record_open("gui"); |     app->gui = furi_record_open("gui"); | ||||||
| @ -83,13 +64,11 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { | |||||||
|         app->error = BadUsbAppErrorCloseRpc; |         app->error = BadUsbAppErrorCloseRpc; | ||||||
|         scene_manager_next_scene(app->scene_manager, BadUsbSceneError); |         scene_manager_next_scene(app->scene_manager, BadUsbSceneError); | ||||||
|     } else { |     } else { | ||||||
|         if(*app->file_name != '\0') { |         if(!string_empty_p(app->file_path)) { | ||||||
|             scene_manager_next_scene(app->scene_manager, BadUsbSceneWork); |             scene_manager_next_scene(app->scene_manager, BadUsbSceneWork); | ||||||
|         } else if(bad_usb_check_assets()) { |  | ||||||
|             scene_manager_next_scene(app->scene_manager, BadUsbSceneFileSelect); |  | ||||||
|         } else { |         } else { | ||||||
|             app->error = BadUsbAppErrorNoFiles; |             string_set_str(app->file_path, BAD_USB_APP_PATH_FOLDER); | ||||||
|             scene_manager_next_scene(app->scene_manager, BadUsbSceneError); |             scene_manager_next_scene(app->scene_manager, BadUsbSceneFileSelect); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -117,6 +96,8 @@ void bad_usb_app_free(BadUsbApp* app) { | |||||||
|     furi_record_close("notification"); |     furi_record_close("notification"); | ||||||
|     furi_record_close("dialogs"); |     furi_record_close("dialogs"); | ||||||
| 
 | 
 | ||||||
|  |     string_clear(app->file_path); | ||||||
|  | 
 | ||||||
|     free(app); |     free(app); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -16,7 +16,6 @@ | |||||||
| 
 | 
 | ||||||
| #define BAD_USB_APP_PATH_FOLDER "/any/badusb" | #define BAD_USB_APP_PATH_FOLDER "/any/badusb" | ||||||
| #define BAD_USB_APP_EXTENSION ".txt" | #define BAD_USB_APP_EXTENSION ".txt" | ||||||
| #define BAD_USB_FILE_NAME_LEN 40 |  | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     BadUsbAppErrorNoFiles, |     BadUsbAppErrorNoFiles, | ||||||
| @ -32,7 +31,7 @@ struct BadUsbApp { | |||||||
|     Widget* widget; |     Widget* widget; | ||||||
| 
 | 
 | ||||||
|     BadUsbAppError error; |     BadUsbAppError error; | ||||||
|     char file_name[BAD_USB_FILE_NAME_LEN + 1]; |     string_t file_path; | ||||||
|     BadUsb* bad_usb_view; |     BadUsb* bad_usb_view; | ||||||
|     BadUsbScript* bad_usb_script; |     BadUsbScript* bad_usb_script; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -5,14 +5,16 @@ | |||||||
| static bool bad_usb_file_select(BadUsbApp* bad_usb) { | static bool bad_usb_file_select(BadUsbApp* bad_usb) { | ||||||
|     furi_assert(bad_usb); |     furi_assert(bad_usb); | ||||||
| 
 | 
 | ||||||
|     // Input events and views are managed by file_select
 |     // Input events and views are managed by file_browser
 | ||||||
|     bool res = dialog_file_select_show( |     bool res = dialog_file_browser_show( | ||||||
|         bad_usb->dialogs, |         bad_usb->dialogs, | ||||||
|         BAD_USB_APP_PATH_FOLDER, |         bad_usb->file_path, | ||||||
|  |         bad_usb->file_path, | ||||||
|         BAD_USB_APP_EXTENSION, |         BAD_USB_APP_EXTENSION, | ||||||
|         bad_usb->file_name, |         true, | ||||||
|         sizeof(bad_usb->file_name), |         &I_badusb_10px, | ||||||
|         NULL); |         true); | ||||||
|  | 
 | ||||||
|     return res; |     return res; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,6 +2,8 @@ | |||||||
| #include "../bad_usb_app_i.h" | #include "../bad_usb_app_i.h" | ||||||
| #include "../views/bad_usb_view.h" | #include "../views/bad_usb_view.h" | ||||||
| #include "furi_hal.h" | #include "furi_hal.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | #include "toolbox/path.h" | ||||||
| 
 | 
 | ||||||
| void bad_usb_scene_work_ok_callback(InputType type, void* context) { | void bad_usb_scene_work_ok_callback(InputType type, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| @ -28,10 +30,9 @@ void bad_usb_scene_work_on_enter(void* context) { | |||||||
|     string_t file_name; |     string_t file_name; | ||||||
|     string_init(file_name); |     string_init(file_name); | ||||||
| 
 | 
 | ||||||
|     bad_usb_set_file_name(app->bad_usb_view, app->file_name); |     path_extract_filename(app->file_path, file_name, true); | ||||||
|     string_printf( |     bad_usb_set_file_name(app->bad_usb_view, string_get_cstr(file_name)); | ||||||
|         file_name, "%s/%s%s", BAD_USB_APP_PATH_FOLDER, app->file_name, BAD_USB_APP_EXTENSION); |     app->bad_usb_script = bad_usb_script_open(app->file_path); | ||||||
|     app->bad_usb_script = bad_usb_script_open(file_name); |  | ||||||
| 
 | 
 | ||||||
|     string_clear(file_name); |     string_clear(file_name); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,6 +2,8 @@ | |||||||
| #include "../bad_usb_script.h" | #include "../bad_usb_script.h" | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| 
 | 
 | ||||||
|  | #define MAX_NAME_LEN 64 | ||||||
|  | 
 | ||||||
| struct BadUsb { | struct BadUsb { | ||||||
|     View* view; |     View* view; | ||||||
|     BadUsbOkCallback callback; |     BadUsbOkCallback callback; | ||||||
| @ -9,7 +11,7 @@ struct BadUsb { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     char* file_name; |     char file_name[MAX_NAME_LEN]; | ||||||
|     BadUsbState state; |     BadUsbState state; | ||||||
|     uint8_t anim_frame; |     uint8_t anim_frame; | ||||||
| } BadUsbModel; | } BadUsbModel; | ||||||
| @ -149,11 +151,11 @@ void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* c | |||||||
|         }); |         }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bad_usb_set_file_name(BadUsb* bad_usb, char* name) { | void bad_usb_set_file_name(BadUsb* bad_usb, const char* name) { | ||||||
|     furi_assert(name); |     furi_assert(name); | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         bad_usb->view, (BadUsbModel * model) { |         bad_usb->view, (BadUsbModel * model) { | ||||||
|             model->file_name = name; |             strncpy(model->file_name, name, MAX_NAME_LEN); | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
| } | } | ||||||
|  | |||||||
| @ -14,6 +14,6 @@ View* bad_usb_get_view(BadUsb* bad_usb); | |||||||
| 
 | 
 | ||||||
| void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* context); | void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* context); | ||||||
| 
 | 
 | ||||||
| void bad_usb_set_file_name(BadUsb* bad_usb, char* name); | void bad_usb_set_file_name(BadUsb* bad_usb, const char* name); | ||||||
| 
 | 
 | ||||||
| void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st); | void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st); | ||||||
|  | |||||||
							
								
								
									
										31
									
								
								applications/bt/bt_service/bt.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										31
									
								
								applications/bt/bt_service/bt.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -80,7 +80,7 @@ static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) { | |||||||
|     string_init_printf(pin_str, "Verify code\n%06d", pin); |     string_init_printf(pin_str, "Verify code\n%06d", pin); | ||||||
|     dialog_message_set_text( |     dialog_message_set_text( | ||||||
|         bt->dialog_message, string_get_cstr(pin_str), 64, 4, AlignCenter, AlignTop); |         bt->dialog_message, string_get_cstr(pin_str), 64, 4, AlignCenter, AlignTop); | ||||||
|     dialog_message_set_buttons(bt->dialog_message, "Cancel", "Ok", NULL); |     dialog_message_set_buttons(bt->dialog_message, "Cancel", "OK", NULL); | ||||||
|     DialogMessageButton button = dialog_message_show(bt->dialogs, bt->dialog_message); |     DialogMessageButton button = dialog_message_show(bt->dialogs, bt->dialog_message); | ||||||
|     string_clear(pin_str); |     string_clear(pin_str); | ||||||
|     return button == DialogMessageButtonCenter; |     return button == DialogMessageButtonCenter; | ||||||
| @ -91,11 +91,16 @@ static void bt_battery_level_changed_callback(const void* _event, void* context) | |||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| 
 | 
 | ||||||
|     Bt* bt = context; |     Bt* bt = context; | ||||||
|  |     BtMessage message = {}; | ||||||
|     const PowerEvent* event = _event; |     const PowerEvent* event = _event; | ||||||
|     if(event->type == PowerEventTypeBatteryLevelChanged) { |     if(event->type == PowerEventTypeBatteryLevelChanged) { | ||||||
|         BtMessage message = { |         message.type = BtMessageTypeUpdateBatteryLevel; | ||||||
|             .type = BtMessageTypeUpdateBatteryLevel, |         message.data.battery_level = event->data.battery_level; | ||||||
|             .data.battery_level = event->data.battery_level}; |         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|  |     } else if( | ||||||
|  |         event->type == PowerEventTypeStartCharging || event->type == PowerEventTypeFullyCharged || | ||||||
|  |         event->type == PowerEventTypeStopCharging) { | ||||||
|  |         message.type = BtMessageTypeUpdatePowerState; | ||||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); |         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -167,7 +172,11 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt | |||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     Bt* bt = context; |     Bt* bt = context; | ||||||
| 
 | 
 | ||||||
|     osEventFlagsClear(bt->rpc_event, BT_RPC_EVENT_ALL); |     if(osEventFlagsGet(bt->rpc_event) & BT_RPC_EVENT_DISCONNECTED) { | ||||||
|  |         // Early stop from sending if we're already disconnected
 | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     osEventFlagsClear(bt->rpc_event, BT_RPC_EVENT_ALL & (~BT_RPC_EVENT_DISCONNECTED)); | ||||||
|     size_t bytes_sent = 0; |     size_t bytes_sent = 0; | ||||||
|     while(bytes_sent < bytes_len) { |     while(bytes_sent < bytes_len) { | ||||||
|         size_t bytes_remain = bytes_len - bytes_sent; |         size_t bytes_remain = bytes_len - bytes_sent; | ||||||
| @ -178,10 +187,14 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt | |||||||
|             furi_hal_bt_serial_tx(&bytes[bytes_sent], bytes_remain); |             furi_hal_bt_serial_tx(&bytes[bytes_sent], bytes_remain); | ||||||
|             bytes_sent += bytes_remain; |             bytes_sent += bytes_remain; | ||||||
|         } |         } | ||||||
|         uint32_t event_flag = |         // We want BT_RPC_EVENT_DISCONNECTED to stick, so don't clear
 | ||||||
|             osEventFlagsWait(bt->rpc_event, BT_RPC_EVENT_ALL, osFlagsWaitAny, osWaitForever); |         uint32_t event_flag = osEventFlagsWait( | ||||||
|  |             bt->rpc_event, BT_RPC_EVENT_ALL, osFlagsWaitAny | osFlagsNoClear, osWaitForever); | ||||||
|         if(event_flag & BT_RPC_EVENT_DISCONNECTED) { |         if(event_flag & BT_RPC_EVENT_DISCONNECTED) { | ||||||
|             break; |             break; | ||||||
|  |         } else { | ||||||
|  |             // If we didn't get BT_RPC_EVENT_DISCONNECTED, then clear everything else
 | ||||||
|  |             osEventFlagsClear(bt->rpc_event, BT_RPC_EVENT_ALL & (~BT_RPC_EVENT_DISCONNECTED)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -197,6 +210,8 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { | |||||||
|         bt->status = BtStatusConnected; |         bt->status = BtStatusConnected; | ||||||
|         BtMessage message = {.type = BtMessageTypeUpdateStatus}; |         BtMessage message = {.type = BtMessageTypeUpdateStatus}; | ||||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); |         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|  |         // Clear BT_RPC_EVENT_DISCONNECTED because it might be set from previous session
 | ||||||
|  |         osEventFlagsClear(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); | ||||||
|         if(bt->profile == BtProfileSerial) { |         if(bt->profile == BtProfileSerial) { | ||||||
|             // Open RPC session
 |             // Open RPC session
 | ||||||
|             bt->rpc_session = rpc_session_open(bt->rpc); |             bt->rpc_session = rpc_session_open(bt->rpc); | ||||||
| @ -368,6 +383,8 @@ int32_t bt_srv() { | |||||||
|         } else if(message.type == BtMessageTypeUpdateBatteryLevel) { |         } else if(message.type == BtMessageTypeUpdateBatteryLevel) { | ||||||
|             // Update battery level
 |             // Update battery level
 | ||||||
|             furi_hal_bt_update_battery_level(message.data.battery_level); |             furi_hal_bt_update_battery_level(message.data.battery_level); | ||||||
|  |         } else if(message.type == BtMessageTypeUpdatePowerState) { | ||||||
|  |             furi_hal_bt_update_power_state(); | ||||||
|         } else if(message.type == BtMessageTypePinCodeShow) { |         } else if(message.type == BtMessageTypePinCodeShow) { | ||||||
|             // Display PIN code
 |             // Display PIN code
 | ||||||
|             bt_pin_code_show(bt, message.data.pin_code); |             bt_pin_code_show(bt, message.data.pin_code); | ||||||
|  | |||||||
| @ -21,6 +21,7 @@ | |||||||
| typedef enum { | typedef enum { | ||||||
|     BtMessageTypeUpdateStatus, |     BtMessageTypeUpdateStatus, | ||||||
|     BtMessageTypeUpdateBatteryLevel, |     BtMessageTypeUpdateBatteryLevel, | ||||||
|  |     BtMessageTypeUpdatePowerState, | ||||||
|     BtMessageTypePinCodeShow, |     BtMessageTypePinCodeShow, | ||||||
|     BtMessageTypeKeysStorageUpdated, |     BtMessageTypeKeysStorageUpdated, | ||||||
|     BtMessageTypeSetProfile, |     BtMessageTypeSetProfile, | ||||||
|  | |||||||
| @ -0,0 +1,99 @@ | |||||||
|  | #include "assets_icons.h" | ||||||
|  | #include "file_browser_app_i.h" | ||||||
|  | #include "gui/modules/file_browser.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | #include <furi.h> | ||||||
|  | #include <furi_hal.h> | ||||||
|  | #include <storage/storage.h> | ||||||
|  | #include <lib/toolbox/path.h> | ||||||
|  | 
 | ||||||
|  | static bool file_browser_app_custom_event_callback(void* context, uint32_t event) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     return scene_manager_handle_custom_event(app->scene_manager, event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool file_browser_app_back_event_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     return scene_manager_handle_back_event(app->scene_manager); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void file_browser_app_tick_event_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     scene_manager_handle_tick_event(app->scene_manager); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FileBrowserApp* file_browser_app_alloc(char* arg) { | ||||||
|  |     UNUSED(arg); | ||||||
|  |     FileBrowserApp* app = malloc(sizeof(FileBrowserApp)); | ||||||
|  | 
 | ||||||
|  |     app->gui = furi_record_open("gui"); | ||||||
|  |     app->dialogs = furi_record_open("dialogs"); | ||||||
|  | 
 | ||||||
|  |     app->view_dispatcher = view_dispatcher_alloc(); | ||||||
|  |     view_dispatcher_enable_queue(app->view_dispatcher); | ||||||
|  | 
 | ||||||
|  |     app->scene_manager = scene_manager_alloc(&file_browser_scene_handlers, app); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_set_event_callback_context(app->view_dispatcher, app); | ||||||
|  |     view_dispatcher_set_tick_event_callback( | ||||||
|  |         app->view_dispatcher, file_browser_app_tick_event_callback, 500); | ||||||
|  |     view_dispatcher_set_custom_event_callback( | ||||||
|  |         app->view_dispatcher, file_browser_app_custom_event_callback); | ||||||
|  |     view_dispatcher_set_navigation_event_callback( | ||||||
|  |         app->view_dispatcher, file_browser_app_back_event_callback); | ||||||
|  | 
 | ||||||
|  |     app->widget = widget_alloc(); | ||||||
|  | 
 | ||||||
|  |     string_init(app->file_path); | ||||||
|  |     app->file_browser = file_browser_alloc(app->file_path); | ||||||
|  |     file_browser_configure(app->file_browser, "*", true, &I_badusb_10px, true); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, FileBrowserAppViewStart, widget_get_view(app->widget)); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, FileBrowserAppViewResult, widget_get_view(app->widget)); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, FileBrowserAppViewBrowser, file_browser_get_view(app->file_browser)); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); | ||||||
|  | 
 | ||||||
|  |     scene_manager_next_scene(app->scene_manager, FileBrowserSceneStart); | ||||||
|  | 
 | ||||||
|  |     return app; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_app_free(FileBrowserApp* app) { | ||||||
|  |     furi_assert(app); | ||||||
|  | 
 | ||||||
|  |     // Views
 | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, FileBrowserAppViewStart); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, FileBrowserAppViewResult); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, FileBrowserAppViewBrowser); | ||||||
|  |     widget_free(app->widget); | ||||||
|  |     file_browser_free(app->file_browser); | ||||||
|  | 
 | ||||||
|  |     // View dispatcher
 | ||||||
|  |     view_dispatcher_free(app->view_dispatcher); | ||||||
|  |     scene_manager_free(app->scene_manager); | ||||||
|  | 
 | ||||||
|  |     // Close records
 | ||||||
|  |     furi_record_close("gui"); | ||||||
|  |     furi_record_close("notification"); | ||||||
|  |     furi_record_close("dialogs"); | ||||||
|  | 
 | ||||||
|  |     string_clear(app->file_path); | ||||||
|  | 
 | ||||||
|  |     free(app); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int32_t file_browser_app(void* p) { | ||||||
|  |     FileBrowserApp* file_browser_app = file_browser_app_alloc((char*)p); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_run(file_browser_app->view_dispatcher); | ||||||
|  | 
 | ||||||
|  |     file_browser_app_free(file_browser_app); | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
| @ -0,0 +1,32 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "scenes/file_browser_scene.h" | ||||||
|  | 
 | ||||||
|  | #include <gui/gui.h> | ||||||
|  | #include <gui/view_dispatcher.h> | ||||||
|  | #include <gui/scene_manager.h> | ||||||
|  | #include <gui/modules/submenu.h> | ||||||
|  | #include <gui/modules/file_browser.h> | ||||||
|  | #include <dialogs/dialogs.h> | ||||||
|  | #include <notification/notification_messages.h> | ||||||
|  | #include <gui/modules/variable_item_list.h> | ||||||
|  | #include <gui/modules/widget.h> | ||||||
|  | 
 | ||||||
|  | typedef struct FileBrowserApp FileBrowserApp; | ||||||
|  | 
 | ||||||
|  | struct FileBrowserApp { | ||||||
|  |     Gui* gui; | ||||||
|  |     ViewDispatcher* view_dispatcher; | ||||||
|  |     SceneManager* scene_manager; | ||||||
|  |     DialogsApp* dialogs; | ||||||
|  |     Widget* widget; | ||||||
|  |     FileBrowser* file_browser; | ||||||
|  | 
 | ||||||
|  |     string_t file_path; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     FileBrowserAppViewStart, | ||||||
|  |     FileBrowserAppViewBrowser, | ||||||
|  |     FileBrowserAppViewResult, | ||||||
|  | } FileBrowserAppView; | ||||||
| @ -0,0 +1,30 @@ | |||||||
|  | #include "file_browser_scene.h" | ||||||
|  | 
 | ||||||
|  | // Generate scene on_enter handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, | ||||||
|  | void (*const file_browser_scene_on_enter_handlers[])(void*) = { | ||||||
|  | #include "file_browser_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_event handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, | ||||||
|  | bool (*const file_browser_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { | ||||||
|  | #include "file_browser_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_exit handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, | ||||||
|  | void (*const file_browser_scene_on_exit_handlers[])(void* context) = { | ||||||
|  | #include "file_browser_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Initialize scene handlers configuration structure
 | ||||||
|  | const SceneManagerHandlers file_browser_scene_handlers = { | ||||||
|  |     .on_enter_handlers = file_browser_scene_on_enter_handlers, | ||||||
|  |     .on_event_handlers = file_browser_scene_on_event_handlers, | ||||||
|  |     .on_exit_handlers = file_browser_scene_on_exit_handlers, | ||||||
|  |     .scene_num = FileBrowserSceneNum, | ||||||
|  | }; | ||||||
| @ -0,0 +1,29 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/scene_manager.h> | ||||||
|  | 
 | ||||||
|  | // Generate scene id and total number
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) FileBrowserScene##id, | ||||||
|  | typedef enum { | ||||||
|  | #include "file_browser_scene_config.h" | ||||||
|  |     FileBrowserSceneNum, | ||||||
|  | } FileBrowserScene; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | extern const SceneManagerHandlers file_browser_scene_handlers; | ||||||
|  | 
 | ||||||
|  | // Generate scene on_enter handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); | ||||||
|  | #include "file_browser_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_event handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) \ | ||||||
|  |     bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); | ||||||
|  | #include "file_browser_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_exit handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); | ||||||
|  | #include "file_browser_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
| @ -0,0 +1,43 @@ | |||||||
|  | #include "../file_browser_app_i.h" | ||||||
|  | #include "furi/check.h" | ||||||
|  | #include "furi/log.h" | ||||||
|  | #include "furi_hal.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | 
 | ||||||
|  | #define DEFAULT_PATH "/" | ||||||
|  | #define EXTENSION "*" | ||||||
|  | 
 | ||||||
|  | bool file_browser_scene_browser_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     UNUSED(context); | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         scene_manager_next_scene(app->scene_manager, FileBrowserSceneResult); | ||||||
|  |         consumed = true; | ||||||
|  |     } else if(event.type == SceneManagerEventTypeTick) { | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void file_browser_callback(void* context) { | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     furi_assert(app); | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, SceneManagerEventTypeCustom); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_scene_browser_on_enter(void* context) { | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  | 
 | ||||||
|  |     file_browser_set_callback(app->file_browser, file_browser_callback, app); | ||||||
|  | 
 | ||||||
|  |     file_browser_start(app->file_browser, app->file_path); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, FileBrowserAppViewBrowser); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_scene_browser_on_exit(void* context) { | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  | 
 | ||||||
|  |     file_browser_stop(app->file_browser); | ||||||
|  | } | ||||||
| @ -0,0 +1,3 @@ | |||||||
|  | ADD_SCENE(file_browser, start, Start) | ||||||
|  | ADD_SCENE(file_browser, browser, Browser) | ||||||
|  | ADD_SCENE(file_browser, result, Result) | ||||||
| @ -0,0 +1,36 @@ | |||||||
|  | #include "../file_browser_app_i.h" | ||||||
|  | #include "furi_hal.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | 
 | ||||||
|  | void file_browser_scene_result_ok_callback(InputType type, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, type); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool file_browser_scene_result_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     UNUSED(context); | ||||||
|  |     //FileBrowserApp* app = context;
 | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         consumed = true; | ||||||
|  |     } else if(event.type == SceneManagerEventTypeTick) { | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_scene_result_on_enter(void* context) { | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  | 
 | ||||||
|  |     widget_add_string_multiline_element( | ||||||
|  |         app->widget, 64, 10, AlignCenter, AlignTop, FontSecondary, string_get_cstr(app->file_path)); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, FileBrowserAppViewResult); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_scene_result_on_exit(void* context) { | ||||||
|  |     UNUSED(context); | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     widget_reset(app->widget); | ||||||
|  | } | ||||||
| @ -0,0 +1,44 @@ | |||||||
|  | #include "../file_browser_app_i.h" | ||||||
|  | #include "furi_hal.h" | ||||||
|  | #include "gui/modules/widget_elements/widget_element_i.h" | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  |     file_browser_scene_start_ok_callback(GuiButtonType result, InputType type, void* context) { | ||||||
|  |     UNUSED(result); | ||||||
|  |     furi_assert(context); | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     if(type == InputTypeShort) { | ||||||
|  |         view_dispatcher_send_custom_event(app->view_dispatcher, type); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool file_browser_scene_start_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         string_set_str(app->file_path, "/any/badusb/demo_windows.txt"); | ||||||
|  |         scene_manager_next_scene(app->scene_manager, FileBrowserSceneBrowser); | ||||||
|  |         consumed = true; | ||||||
|  |     } else if(event.type == SceneManagerEventTypeTick) { | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_scene_start_on_enter(void* context) { | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  | 
 | ||||||
|  |     widget_add_string_multiline_element( | ||||||
|  |         app->widget, 64, 20, AlignCenter, AlignTop, FontSecondary, "Press OK to start"); | ||||||
|  | 
 | ||||||
|  |     widget_add_button_element( | ||||||
|  |         app->widget, GuiButtonTypeCenter, "Ok", file_browser_scene_start_ok_callback, app); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, FileBrowserAppViewStart); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_scene_start_on_exit(void* context) { | ||||||
|  |     UNUSED(context); | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     widget_reset(app->widget); | ||||||
|  | } | ||||||
| @ -100,11 +100,12 @@ void desktop_pin_lock_init(DesktopSettings* settings) { | |||||||
|     } else { |     } else { | ||||||
|         furi_hal_rtc_set_pin_fails(0); |         furi_hal_rtc_set_pin_fails(0); | ||||||
|         furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); |         furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); | ||||||
|         furi_hal_usb_enable(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(desktop_pin_lock_is_locked()) { |     if(desktop_pin_lock_is_locked()) { | ||||||
|         furi_hal_usb_disable(); |         Cli* cli = furi_record_open("cli"); | ||||||
|  |         cli_session_close(cli); | ||||||
|  |         furi_record_close("cli"); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -125,7 +125,9 @@ static void desktop_view_locked_draw(Canvas* canvas, void* model) { | |||||||
|         canvas_set_font(canvas, FontSecondary); |         canvas_set_font(canvas, FontSecondary); | ||||||
|         elements_bold_rounded_frame(canvas, 14, 2 + STATUS_BAR_Y_SHIFT, 99, 48); |         elements_bold_rounded_frame(canvas, 14, 2 + STATUS_BAR_Y_SHIFT, 99, 48); | ||||||
|         elements_multiline_text(canvas, 65, 20 + STATUS_BAR_Y_SHIFT, "To unlock\npress:"); |         elements_multiline_text(canvas, 65, 20 + STATUS_BAR_Y_SHIFT, "To unlock\npress:"); | ||||||
|         canvas_draw_icon(canvas, 65, 36 + STATUS_BAR_Y_SHIFT, &I_Back3_45x8); |         canvas_draw_icon(canvas, 65, 36 + STATUS_BAR_Y_SHIFT, &I_Pin_back_arrow_10x8); | ||||||
|  |         canvas_draw_icon(canvas, 80, 36 + STATUS_BAR_Y_SHIFT, &I_Pin_back_arrow_10x8); | ||||||
|  |         canvas_draw_icon(canvas, 95, 36 + STATUS_BAR_Y_SHIFT, &I_Pin_back_arrow_10x8); | ||||||
|         canvas_draw_icon(canvas, 16, 7 + STATUS_BAR_Y_SHIFT, &I_WarningDolphin_45x42); |         canvas_draw_icon(canvas, 16, 7 + STATUS_BAR_Y_SHIFT, &I_WarningDolphin_45x42); | ||||||
|         canvas_draw_dot(canvas, 17, 61); |         canvas_draw_dot(canvas, 17, 61); | ||||||
|     } else if(view_state == DesktopViewLockedStateUnlockedHintShown) { |     } else if(view_state == DesktopViewLockedStateUnlockedHintShown) { | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
|  | #include "dialogs/dialogs_message.h" | ||||||
| #include "dialogs_i.h" | #include "dialogs_i.h" | ||||||
| #include "dialogs_api_lock.h" | #include "dialogs_api_lock.h" | ||||||
| #include "dialogs_module_file_select.h" | #include "dialogs_module_file_browser.h" | ||||||
| #include "dialogs_module_message.h" | #include "dialogs_module_message.h" | ||||||
| 
 | 
 | ||||||
| static DialogsApp* dialogs_app_alloc() { | static DialogsApp* dialogs_app_alloc() { | ||||||
| @ -13,9 +14,9 @@ static DialogsApp* dialogs_app_alloc() { | |||||||
| static void dialogs_app_process_message(DialogsApp* app, DialogsAppMessage* message) { | static void dialogs_app_process_message(DialogsApp* app, DialogsAppMessage* message) { | ||||||
|     UNUSED(app); |     UNUSED(app); | ||||||
|     switch(message->command) { |     switch(message->command) { | ||||||
|     case DialogsAppCommandFileOpen: |     case DialogsAppCommandFileBrowser: | ||||||
|         message->return_data->bool_value = |         message->return_data->bool_value = | ||||||
|             dialogs_app_process_module_file_select(&message->data->file_select); |             dialogs_app_process_module_file_browser(&message->data->file_browser); | ||||||
|         break; |         break; | ||||||
|     case DialogsAppCommandDialog: |     case DialogsAppCommandDialog: | ||||||
|         message->return_data->dialog_value = |         message->return_data->dialog_value = | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <gui/canvas.h> | #include <gui/canvas.h> | ||||||
|  | #include "m-string.h" | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| @ -10,25 +11,27 @@ extern "C" { | |||||||
| 
 | 
 | ||||||
| typedef struct DialogsApp DialogsApp; | typedef struct DialogsApp DialogsApp; | ||||||
| 
 | 
 | ||||||
| /****************** FILE SELECT ******************/ | /****************** FILE BROWSER ******************/ | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Shows and processes the file selection dialog |  * Shows and processes the file browser dialog | ||||||
|  * @param context api pointer |  * @param context api pointer | ||||||
|  * @param path path to directory |  * @param result_path selected file path string pointer | ||||||
|  |  * @param path preselected file path string pointer | ||||||
|  * @param extension file extension to be offered for selection |  * @param extension file extension to be offered for selection | ||||||
|  * @param selected_filename buffer where the selected filename will be saved |  * @param skip_assets true - do not show assets folders | ||||||
|  * @param selected_filename_size and the size of this buffer |  * @param icon file icon pointer, NULL for default icon | ||||||
|  * @param preselected_filename filename to be preselected |  * @param hide_ext true - hide extensions for files | ||||||
|  * @return bool whether a file was selected |  * @return bool whether a file was selected | ||||||
|  */ |  */ | ||||||
| bool dialog_file_select_show( | bool dialog_file_browser_show( | ||||||
|     DialogsApp* context, |     DialogsApp* context, | ||||||
|     const char* path, |     string_ptr result_path, | ||||||
|  |     string_ptr path, | ||||||
|     const char* extension, |     const char* extension, | ||||||
|     char* result, |     bool skip_assets, | ||||||
|     uint8_t result_size, |     const Icon* icon, | ||||||
|     const char* preselected_filename); |     bool hide_ext); | ||||||
| 
 | 
 | ||||||
| /****************** MESSAGE ******************/ | /****************** MESSAGE ******************/ | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,31 +1,36 @@ | |||||||
|  | #include "dialogs/dialogs_message.h" | ||||||
| #include "dialogs_i.h" | #include "dialogs_i.h" | ||||||
| #include "dialogs_api_lock.h" | #include "dialogs_api_lock.h" | ||||||
|  | #include "m-string.h" | ||||||
| 
 | 
 | ||||||
| /****************** File select ******************/ | /****************** File browser ******************/ | ||||||
| 
 | 
 | ||||||
| bool dialog_file_select_show( | bool dialog_file_browser_show( | ||||||
|     DialogsApp* context, |     DialogsApp* context, | ||||||
|     const char* path, |     string_ptr result_path, | ||||||
|  |     string_ptr path, | ||||||
|     const char* extension, |     const char* extension, | ||||||
|     char* result, |     bool skip_assets, | ||||||
|     uint8_t result_size, |     const Icon* icon, | ||||||
|     const char* preselected_filename) { |     bool hide_ext) { | ||||||
|     FuriApiLock lock = API_LOCK_INIT_LOCKED(); |     FuriApiLock lock = API_LOCK_INIT_LOCKED(); | ||||||
|     furi_check(lock != NULL); |     furi_check(lock != NULL); | ||||||
| 
 | 
 | ||||||
|     DialogsAppData data = { |     DialogsAppData data = { | ||||||
|         .file_select = { |         .file_browser = { | ||||||
|             .path = path, |  | ||||||
|             .extension = extension, |             .extension = extension, | ||||||
|             .result = result, |             .result_path = result_path, | ||||||
|             .result_size = result_size, |             .file_icon = icon, | ||||||
|             .preselected_filename = preselected_filename, |             .hide_ext = hide_ext, | ||||||
|  |             .skip_assets = skip_assets, | ||||||
|  |             .preselected_filename = path, | ||||||
|  | 
 | ||||||
|         }}; |         }}; | ||||||
| 
 | 
 | ||||||
|     DialogsAppReturn return_data; |     DialogsAppReturn return_data; | ||||||
|     DialogsAppMessage message = { |     DialogsAppMessage message = { | ||||||
|         .lock = lock, |         .lock = lock, | ||||||
|         .command = DialogsAppCommandFileOpen, |         .command = DialogsAppCommandFileBrowser, | ||||||
|         .data = &data, |         .data = &data, | ||||||
|         .return_data = &return_data, |         .return_data = &return_data, | ||||||
|     }; |     }; | ||||||
|  | |||||||
| @ -2,25 +2,27 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include "dialogs_i.h" | #include "dialogs_i.h" | ||||||
| #include "dialogs_api_lock.h" | #include "dialogs_api_lock.h" | ||||||
|  | #include "m-string.h" | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     const char* path; |  | ||||||
|     const char* extension; |     const char* extension; | ||||||
|     char* result; |     bool skip_assets; | ||||||
|     uint8_t result_size; |     bool hide_ext; | ||||||
|     const char* preselected_filename; |     const Icon* file_icon; | ||||||
| } DialogsAppMessageDataFileSelect; |     string_ptr result_path; | ||||||
|  |     string_ptr preselected_filename; | ||||||
|  | } DialogsAppMessageDataFileBrowser; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     const DialogMessage* message; |     const DialogMessage* message; | ||||||
| } DialogsAppMessageDataDialog; | } DialogsAppMessageDataDialog; | ||||||
| 
 | 
 | ||||||
| typedef union { | typedef union { | ||||||
|     DialogsAppMessageDataFileSelect file_select; |     DialogsAppMessageDataFileBrowser file_browser; | ||||||
|     DialogsAppMessageDataDialog dialog; |     DialogsAppMessageDataDialog dialog; | ||||||
| } DialogsAppData; | } DialogsAppData; | ||||||
| 
 | 
 | ||||||
| @ -30,7 +32,7 @@ typedef union { | |||||||
| } DialogsAppReturn; | } DialogsAppReturn; | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     DialogsAppCommandFileOpen, |     DialogsAppCommandFileBrowser, | ||||||
|     DialogsAppCommandDialog, |     DialogsAppCommandDialog, | ||||||
| } DialogsAppCommand; | } DialogsAppCommand; | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										59
									
								
								applications/dialogs/dialogs_module_file_browser.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								applications/dialogs/dialogs_module_file_browser.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | |||||||
|  | #include "dialogs_i.h" | ||||||
|  | #include "dialogs_api_lock.h" | ||||||
|  | #include "gui/modules/file_browser.h" | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     FuriApiLock lock; | ||||||
|  |     bool result; | ||||||
|  | } DialogsAppFileBrowserContext; | ||||||
|  | 
 | ||||||
|  | static void dialogs_app_file_browser_back_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     DialogsAppFileBrowserContext* file_browser_context = context; | ||||||
|  |     file_browser_context->result = false; | ||||||
|  |     API_LOCK_UNLOCK(file_browser_context->lock); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void dialogs_app_file_browser_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     DialogsAppFileBrowserContext* file_browser_context = context; | ||||||
|  |     file_browser_context->result = true; | ||||||
|  |     API_LOCK_UNLOCK(file_browser_context->lock); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrowser* data) { | ||||||
|  |     bool ret = false; | ||||||
|  |     Gui* gui = furi_record_open("gui"); | ||||||
|  | 
 | ||||||
|  |     DialogsAppFileBrowserContext* file_browser_context = | ||||||
|  |         malloc(sizeof(DialogsAppFileBrowserContext)); | ||||||
|  |     file_browser_context->lock = API_LOCK_INIT_LOCKED(); | ||||||
|  | 
 | ||||||
|  |     ViewHolder* view_holder = view_holder_alloc(); | ||||||
|  |     view_holder_attach_to_gui(view_holder, gui); | ||||||
|  |     view_holder_set_back_callback( | ||||||
|  |         view_holder, dialogs_app_file_browser_back_callback, file_browser_context); | ||||||
|  | 
 | ||||||
|  |     FileBrowser* file_browser = file_browser_alloc(data->result_path); | ||||||
|  |     file_browser_set_callback( | ||||||
|  |         file_browser, dialogs_app_file_browser_callback, file_browser_context); | ||||||
|  |     file_browser_configure( | ||||||
|  |         file_browser, data->extension, data->skip_assets, data->file_icon, data->hide_ext); | ||||||
|  |     file_browser_start(file_browser, data->preselected_filename); | ||||||
|  | 
 | ||||||
|  |     view_holder_set_view(view_holder, file_browser_get_view(file_browser)); | ||||||
|  |     view_holder_start(view_holder); | ||||||
|  |     API_LOCK_WAIT_UNTIL_UNLOCK(file_browser_context->lock); | ||||||
|  | 
 | ||||||
|  |     ret = file_browser_context->result; | ||||||
|  | 
 | ||||||
|  |     view_holder_stop(view_holder); | ||||||
|  |     view_holder_free(view_holder); | ||||||
|  |     file_browser_stop(file_browser); | ||||||
|  |     file_browser_free(file_browser); | ||||||
|  |     API_LOCK_FREE(file_browser_context->lock); | ||||||
|  |     free(file_browser_context); | ||||||
|  |     furi_record_close("gui"); | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
| @ -5,7 +5,7 @@ | |||||||
| extern "C" { | extern "C" { | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelect* data); | bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrowser* data); | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
| @ -1,59 +0,0 @@ | |||||||
| #include "dialogs_i.h" |  | ||||||
| #include "dialogs_api_lock.h" |  | ||||||
| #include <gui/modules/file_select.h> |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     FuriApiLock lock; |  | ||||||
|     bool result; |  | ||||||
| } DialogsAppFileSelectContext; |  | ||||||
| 
 |  | ||||||
| static void dialogs_app_file_select_back_callback(void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     DialogsAppFileSelectContext* file_select_context = context; |  | ||||||
|     file_select_context->result = false; |  | ||||||
|     API_LOCK_UNLOCK(file_select_context->lock); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void dialogs_app_file_select_callback(bool result, void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     DialogsAppFileSelectContext* file_select_context = context; |  | ||||||
|     file_select_context->result = result; |  | ||||||
|     API_LOCK_UNLOCK(file_select_context->lock); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelect* data) { |  | ||||||
|     bool ret = false; |  | ||||||
|     Gui* gui = furi_record_open("gui"); |  | ||||||
| 
 |  | ||||||
|     DialogsAppFileSelectContext* file_select_context = malloc(sizeof(DialogsAppFileSelectContext)); |  | ||||||
|     file_select_context->lock = API_LOCK_INIT_LOCKED(); |  | ||||||
| 
 |  | ||||||
|     ViewHolder* view_holder = view_holder_alloc(); |  | ||||||
|     view_holder_attach_to_gui(view_holder, gui); |  | ||||||
|     view_holder_set_back_callback( |  | ||||||
|         view_holder, dialogs_app_file_select_back_callback, file_select_context); |  | ||||||
| 
 |  | ||||||
|     FileSelect* file_select = file_select_alloc(); |  | ||||||
|     file_select_set_callback(file_select, dialogs_app_file_select_callback, file_select_context); |  | ||||||
|     file_select_set_filter(file_select, data->path, data->extension); |  | ||||||
|     file_select_set_result_buffer(file_select, data->result, data->result_size); |  | ||||||
|     file_select_init(file_select); |  | ||||||
|     if(data->preselected_filename != NULL) { |  | ||||||
|         file_select_set_selected_file(file_select, data->preselected_filename); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     view_holder_set_view(view_holder, file_select_get_view(file_select)); |  | ||||||
|     view_holder_start(view_holder); |  | ||||||
|     API_LOCK_WAIT_UNTIL_UNLOCK(file_select_context->lock); |  | ||||||
| 
 |  | ||||||
|     ret = file_select_context->result; |  | ||||||
| 
 |  | ||||||
|     view_holder_stop(view_holder); |  | ||||||
|     view_holder_free(view_holder); |  | ||||||
|     file_select_free(file_select); |  | ||||||
|     API_LOCK_FREE(file_select_context->lock); |  | ||||||
|     free(file_select_context); |  | ||||||
|     furi_record_close("gui"); |  | ||||||
| 
 |  | ||||||
|     return ret; |  | ||||||
| } |  | ||||||
| @ -72,19 +72,8 @@ bool dolphin_state_load(DolphinState* dolphin_state) { | |||||||
| 
 | 
 | ||||||
| uint64_t dolphin_state_timestamp() { | uint64_t dolphin_state_timestamp() { | ||||||
|     FuriHalRtcDateTime datetime; |     FuriHalRtcDateTime datetime; | ||||||
|     struct tm current; |  | ||||||
| 
 |  | ||||||
|     furi_hal_rtc_get_datetime(&datetime); |     furi_hal_rtc_get_datetime(&datetime); | ||||||
| 
 |     return furi_hal_rtc_datetime_to_timestamp(&datetime); | ||||||
|     current.tm_year = datetime.year - 1900; |  | ||||||
|     current.tm_mday = datetime.day; |  | ||||||
|     current.tm_mon = datetime.month - 1; |  | ||||||
| 
 |  | ||||||
|     current.tm_hour = datetime.hour; |  | ||||||
|     current.tm_min = datetime.minute; |  | ||||||
|     current.tm_sec = datetime.second; |  | ||||||
| 
 |  | ||||||
|     return mktime(¤t); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool dolphin_state_is_levelup(uint32_t icounter) { | bool dolphin_state_is_levelup(uint32_t icounter) { | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
| typedef enum { | typedef enum { | ||||||
|     GpioStartEventOtgOff = 0, |     GpioStartEventOtgOff = 0, | ||||||
|     GpioStartEventOtgOn, |     GpioStartEventOtgOn, | ||||||
|     GpioStartEventManualConrol, |     GpioStartEventManualControl, | ||||||
|     GpioStartEventUsbUart, |     GpioStartEventUsbUart, | ||||||
| 
 | 
 | ||||||
|     GpioCustomEventErrorBack, |     GpioCustomEventErrorBack, | ||||||
|  | |||||||
| @ -15,15 +15,15 @@ enum GpioOtg { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const char* const gpio_otg_text[GpioOtgSettingsNum] = { | const char* const gpio_otg_text[GpioOtgSettingsNum] = { | ||||||
|     "Off", |     "OFF", | ||||||
|     "On", |     "ON", | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static void gpio_scene_start_var_list_enter_callback(void* context, uint32_t index) { | static void gpio_scene_start_var_list_enter_callback(void* context, uint32_t index) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     GpioApp* app = context; |     GpioApp* app = context; | ||||||
|     if(index == GpioItemTest) { |     if(index == GpioItemTest) { | ||||||
|         view_dispatcher_send_custom_event(app->view_dispatcher, GpioStartEventManualConrol); |         view_dispatcher_send_custom_event(app->view_dispatcher, GpioStartEventManualControl); | ||||||
|     } else if(index == GpioItemUsbUart) { |     } else if(index == GpioItemUsbUart) { | ||||||
|         view_dispatcher_send_custom_event(app->view_dispatcher, GpioStartEventUsbUart); |         view_dispatcher_send_custom_event(app->view_dispatcher, GpioStartEventUsbUart); | ||||||
|     } |     } | ||||||
| @ -82,7 +82,7 @@ bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) { | |||||||
|             furi_hal_power_enable_otg(); |             furi_hal_power_enable_otg(); | ||||||
|         } else if(event.event == GpioStartEventOtgOff) { |         } else if(event.event == GpioStartEventOtgOff) { | ||||||
|             furi_hal_power_disable_otg(); |             furi_hal_power_disable_otg(); | ||||||
|         } else if(event.event == GpioStartEventManualConrol) { |         } else if(event.event == GpioStartEventManualControl) { | ||||||
|             scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, GpioItemTest); |             scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, GpioItemTest); | ||||||
|             scene_manager_next_scene(app->scene_manager, GpioSceneTest); |             scene_manager_next_scene(app->scene_manager, GpioSceneTest); | ||||||
|         } else if(event.event == GpioStartEventUsbUart) { |         } else if(event.event == GpioStartEventUsbUart) { | ||||||
|  | |||||||
| @ -45,8 +45,8 @@ static void dialog_ex_view_draw_callback(Canvas* canvas, void* _model) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Draw header
 |     // Draw header
 | ||||||
|  |     canvas_set_font(canvas, FontPrimary); | ||||||
|     if(model->header.text != NULL) { |     if(model->header.text != NULL) { | ||||||
|         canvas_set_font(canvas, FontPrimary); |  | ||||||
|         elements_multiline_text_aligned( |         elements_multiline_text_aligned( | ||||||
|             canvas, |             canvas, | ||||||
|             model->header.x, |             model->header.x, | ||||||
| @ -57,8 +57,8 @@ static void dialog_ex_view_draw_callback(Canvas* canvas, void* _model) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Draw text
 |     // Draw text
 | ||||||
|  |     canvas_set_font(canvas, FontSecondary); | ||||||
|     if(model->text.text != NULL) { |     if(model->text.text != NULL) { | ||||||
|         canvas_set_font(canvas, FontSecondary); |  | ||||||
|         elements_multiline_text_aligned( |         elements_multiline_text_aligned( | ||||||
|             canvas, |             canvas, | ||||||
|             model->text.x, |             model->text.x, | ||||||
|  | |||||||
							
								
								
									
										534
									
								
								applications/gui/modules/file_browser.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										534
									
								
								applications/gui/modules/file_browser.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,534 @@ | |||||||
|  | #include "file_browser.h" | ||||||
|  | #include "assets_icons.h" | ||||||
|  | #include "cmsis_os2.h" | ||||||
|  | #include "file_browser_worker.h" | ||||||
|  | #include "furi/check.h" | ||||||
|  | #include "furi/common_defines.h" | ||||||
|  | #include "furi/log.h" | ||||||
|  | #include "furi_hal_resources.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | #include <m-array.h> | ||||||
|  | #include <gui/elements.h> | ||||||
|  | #include <furi.h> | ||||||
|  | #include "toolbox/path.h" | ||||||
|  | 
 | ||||||
|  | #define LIST_ITEMS 5u | ||||||
|  | #define MAX_LEN_PX 110 | ||||||
|  | #define FRAME_HEIGHT 12 | ||||||
|  | #define Y_OFFSET 3 | ||||||
|  | 
 | ||||||
|  | #define ITEM_LIST_LEN_MAX 100 | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     BrowserItemTypeLoading, | ||||||
|  |     BrowserItemTypeBack, | ||||||
|  |     BrowserItemTypeFolder, | ||||||
|  |     BrowserItemTypeFile, | ||||||
|  | } BrowserItemType; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     string_t path; | ||||||
|  |     BrowserItemType type; | ||||||
|  | } BrowserItem_t; | ||||||
|  | 
 | ||||||
|  | static void BrowserItem_t_init(BrowserItem_t* obj) { | ||||||
|  |     obj->type = BrowserItemTypeLoading; | ||||||
|  |     string_init(obj->path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void BrowserItem_t_init_set(BrowserItem_t* obj, const BrowserItem_t* src) { | ||||||
|  |     obj->type = src->type; | ||||||
|  |     string_init_set(obj->path, src->path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void BrowserItem_t_set(BrowserItem_t* obj, const BrowserItem_t* src) { | ||||||
|  |     obj->type = src->type; | ||||||
|  |     string_set(obj->path, src->path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void BrowserItem_t_clear(BrowserItem_t* obj) { | ||||||
|  |     string_clear(obj->path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ARRAY_DEF( | ||||||
|  |     items_array, | ||||||
|  |     BrowserItem_t, | ||||||
|  |     (INIT(API_2(BrowserItem_t_init)), | ||||||
|  |      SET(API_6(BrowserItem_t_set)), | ||||||
|  |      INIT_SET(API_6(BrowserItem_t_init_set)), | ||||||
|  |      CLEAR(API_2(BrowserItem_t_clear)))) | ||||||
|  | 
 | ||||||
|  | struct FileBrowser { | ||||||
|  |     View* view; | ||||||
|  |     BrowserWorker* worker; | ||||||
|  |     const char* ext_filter; | ||||||
|  |     bool skip_assets; | ||||||
|  | 
 | ||||||
|  |     FileBrowserCallback callback; | ||||||
|  |     void* context; | ||||||
|  | 
 | ||||||
|  |     string_ptr result_path; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     items_array_t items; | ||||||
|  | 
 | ||||||
|  |     bool is_root; | ||||||
|  |     bool folder_loading; | ||||||
|  |     bool list_loading; | ||||||
|  |     uint32_t item_cnt; | ||||||
|  |     int32_t item_idx; | ||||||
|  |     int32_t array_offset; | ||||||
|  |     int32_t list_offset; | ||||||
|  | 
 | ||||||
|  |     const Icon* file_icon; | ||||||
|  |     bool hide_ext; | ||||||
|  | } FileBrowserModel; | ||||||
|  | 
 | ||||||
|  | static const Icon* BrowserItemIcons[] = { | ||||||
|  |     [BrowserItemTypeLoading] = &I_loading_10px, | ||||||
|  |     [BrowserItemTypeBack] = &I_back_10px, | ||||||
|  |     [BrowserItemTypeFolder] = &I_dir_10px, | ||||||
|  |     [BrowserItemTypeFile] = &I_unknown_10px, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static void file_browser_view_draw_callback(Canvas* canvas, void* _model); | ||||||
|  | static bool file_browser_view_input_callback(InputEvent* event, void* context); | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  |     browser_folder_open_cb(void* context, uint32_t item_cnt, int32_t file_idx, bool is_root); | ||||||
|  | static void browser_list_load_cb(void* context, uint32_t list_load_offset); | ||||||
|  | static void browser_list_item_cb(void* context, string_t item_path, bool is_folder, bool is_last); | ||||||
|  | static void browser_long_load_cb(void* context); | ||||||
|  | 
 | ||||||
|  | FileBrowser* file_browser_alloc(string_ptr result_path) { | ||||||
|  |     furi_assert(result_path); | ||||||
|  |     FileBrowser* browser = malloc(sizeof(FileBrowser)); | ||||||
|  |     browser->view = view_alloc(); | ||||||
|  |     view_allocate_model(browser->view, ViewModelTypeLocking, sizeof(FileBrowserModel)); | ||||||
|  |     view_set_context(browser->view, browser); | ||||||
|  |     view_set_draw_callback(browser->view, file_browser_view_draw_callback); | ||||||
|  |     view_set_input_callback(browser->view, file_browser_view_input_callback); | ||||||
|  | 
 | ||||||
|  |     browser->result_path = result_path; | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (FileBrowserModel * model) { | ||||||
|  |             items_array_init(model->items); | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     return browser; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_free(FileBrowser* browser) { | ||||||
|  |     furi_assert(browser); | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (FileBrowserModel * model) { | ||||||
|  |             items_array_clear(model->items); | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     view_free(browser->view); | ||||||
|  |     free(browser); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | View* file_browser_get_view(FileBrowser* browser) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     return browser->view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_configure( | ||||||
|  |     FileBrowser* browser, | ||||||
|  |     const char* extension, | ||||||
|  |     bool skip_assets, | ||||||
|  |     const Icon* file_icon, | ||||||
|  |     bool hide_ext) { | ||||||
|  |     furi_assert(browser); | ||||||
|  | 
 | ||||||
|  |     browser->ext_filter = extension; | ||||||
|  |     browser->skip_assets = skip_assets; | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (FileBrowserModel * model) { | ||||||
|  |             model->file_icon = file_icon; | ||||||
|  |             model->hide_ext = hide_ext; | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_start(FileBrowser* browser, string_t path) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     browser->worker = file_browser_worker_alloc(path, browser->ext_filter, browser->skip_assets); | ||||||
|  |     file_browser_worker_set_callback_context(browser->worker, browser); | ||||||
|  |     file_browser_worker_set_folder_callback(browser->worker, browser_folder_open_cb); | ||||||
|  |     file_browser_worker_set_list_callback(browser->worker, browser_list_load_cb); | ||||||
|  |     file_browser_worker_set_item_callback(browser->worker, browser_list_item_cb); | ||||||
|  |     file_browser_worker_set_long_load_callback(browser->worker, browser_long_load_cb); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_stop(FileBrowser* browser) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     file_browser_worker_free(browser->worker); | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (FileBrowserModel * model) { | ||||||
|  |             items_array_reset(model->items); | ||||||
|  |             model->item_cnt = 0; | ||||||
|  |             model->item_idx = 0; | ||||||
|  |             model->array_offset = 0; | ||||||
|  |             model->list_offset = 0; | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_set_callback(FileBrowser* browser, FileBrowserCallback callback, void* context) { | ||||||
|  |     browser->context = context; | ||||||
|  |     browser->callback = callback; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool browser_is_item_in_array(FileBrowserModel* model, uint32_t idx) { | ||||||
|  |     size_t array_size = items_array_size(model->items); | ||||||
|  | 
 | ||||||
|  |     if((idx >= (uint32_t)model->array_offset + array_size) || | ||||||
|  |        (idx < (uint32_t)model->array_offset)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool browser_is_list_load_required(FileBrowserModel* model) { | ||||||
|  |     size_t array_size = items_array_size(model->items); | ||||||
|  |     uint32_t item_cnt = (model->is_root) ? model->item_cnt : model->item_cnt - 1; | ||||||
|  | 
 | ||||||
|  |     if((model->list_loading) || (array_size >= item_cnt)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if((model->array_offset > 0) && | ||||||
|  |        (model->item_idx < (model->array_offset + ITEM_LIST_LEN_MAX / 4))) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(((model->array_offset + array_size) < item_cnt) && | ||||||
|  |        (model->item_idx > (int32_t)(model->array_offset + array_size - ITEM_LIST_LEN_MAX / 4))) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void browser_update_offset(FileBrowser* browser) { | ||||||
|  |     furi_assert(browser); | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (FileBrowserModel * model) { | ||||||
|  |             uint16_t bounds = model->item_cnt > (LIST_ITEMS - 1) ? 2 : model->item_cnt; | ||||||
|  | 
 | ||||||
|  |             if((model->item_cnt > (LIST_ITEMS - 1)) && | ||||||
|  |                (model->item_idx >= ((int32_t)model->item_cnt - 1))) { | ||||||
|  |                 model->list_offset = model->item_idx - (LIST_ITEMS - 1); | ||||||
|  |             } else if(model->list_offset < model->item_idx - bounds) { | ||||||
|  |                 model->list_offset = CLAMP( | ||||||
|  |                     model->item_idx - (int32_t)(LIST_ITEMS - 2), | ||||||
|  |                     (int32_t)model->item_cnt - bounds, | ||||||
|  |                     0); | ||||||
|  |             } else if(model->list_offset > model->item_idx - bounds) { | ||||||
|  |                 model->list_offset = | ||||||
|  |                     CLAMP(model->item_idx - 1, (int32_t)model->item_cnt - bounds, 0); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  |     browser_folder_open_cb(void* context, uint32_t item_cnt, int32_t file_idx, bool is_root) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     FileBrowser* browser = (FileBrowser*)context; | ||||||
|  | 
 | ||||||
|  |     int32_t load_offset = 0; | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (FileBrowserModel * model) { | ||||||
|  |             items_array_reset(model->items); | ||||||
|  |             if(is_root) { | ||||||
|  |                 model->item_cnt = item_cnt; | ||||||
|  |                 model->item_idx = (file_idx > 0) ? file_idx : 0; | ||||||
|  |                 load_offset = | ||||||
|  |                     CLAMP(model->item_idx - ITEM_LIST_LEN_MAX / 2, (int32_t)model->item_cnt, 0); | ||||||
|  |             } else { | ||||||
|  |                 model->item_cnt = item_cnt + 1; | ||||||
|  |                 model->item_idx = file_idx + 1; | ||||||
|  |                 load_offset = CLAMP( | ||||||
|  |                     model->item_idx - ITEM_LIST_LEN_MAX / 2 - 1, (int32_t)model->item_cnt - 1, 0); | ||||||
|  |             } | ||||||
|  |             model->array_offset = 0; | ||||||
|  |             model->list_offset = 0; | ||||||
|  |             model->is_root = is_root; | ||||||
|  |             model->list_loading = true; | ||||||
|  |             model->folder_loading = false; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  |     browser_update_offset(browser); | ||||||
|  | 
 | ||||||
|  |     file_browser_worker_load(browser->worker, load_offset, ITEM_LIST_LEN_MAX); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void browser_list_load_cb(void* context, uint32_t list_load_offset) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     FileBrowser* browser = (FileBrowser*)context; | ||||||
|  | 
 | ||||||
|  |     BrowserItem_t back_item; | ||||||
|  |     BrowserItem_t_init(&back_item); | ||||||
|  |     back_item.type = BrowserItemTypeBack; | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (FileBrowserModel * model) { | ||||||
|  |             items_array_reset(model->items); | ||||||
|  |             model->array_offset = list_load_offset; | ||||||
|  |             if(!model->is_root) { | ||||||
|  |                 if(list_load_offset == 0) { | ||||||
|  |                     items_array_push_back(model->items, back_item); | ||||||
|  |                 } else { | ||||||
|  |                     model->array_offset += 1; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     BrowserItem_t_clear(&back_item); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void browser_list_item_cb(void* context, string_t item_path, bool is_folder, bool is_last) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     FileBrowser* browser = (FileBrowser*)context; | ||||||
|  | 
 | ||||||
|  |     BrowserItem_t item; | ||||||
|  | 
 | ||||||
|  |     if(!is_last) { | ||||||
|  |         BrowserItem_t_init(&item); | ||||||
|  |         string_set(item.path, item_path); | ||||||
|  |         item.type = (is_folder) ? BrowserItemTypeFolder : BrowserItemTypeFile; | ||||||
|  | 
 | ||||||
|  |         with_view_model( | ||||||
|  |             browser->view, (FileBrowserModel * model) { | ||||||
|  |                 items_array_push_back(model->items, item); | ||||||
|  |                 return false; | ||||||
|  |             }); | ||||||
|  |         BrowserItem_t_clear(&item); | ||||||
|  |     } else { | ||||||
|  |         with_view_model( | ||||||
|  |             browser->view, (FileBrowserModel * model) { | ||||||
|  |                 model->list_loading = false; | ||||||
|  |                 return true; | ||||||
|  |             }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void browser_long_load_cb(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     FileBrowser* browser = (FileBrowser*)context; | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (FileBrowserModel * model) { | ||||||
|  |             model->folder_loading = true; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void browser_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  |     canvas_draw_box( | ||||||
|  |         canvas, 0, Y_OFFSET + idx * FRAME_HEIGHT, (scrollbar ? 122 : 127), FRAME_HEIGHT); | ||||||
|  | 
 | ||||||
|  |     canvas_set_color(canvas, ColorWhite); | ||||||
|  |     canvas_draw_dot(canvas, 0, Y_OFFSET + idx * FRAME_HEIGHT); | ||||||
|  |     canvas_draw_dot(canvas, 1, Y_OFFSET + idx * FRAME_HEIGHT); | ||||||
|  |     canvas_draw_dot(canvas, 0, (Y_OFFSET + idx * FRAME_HEIGHT) + 1); | ||||||
|  | 
 | ||||||
|  |     canvas_draw_dot(canvas, 0, (Y_OFFSET + idx * FRAME_HEIGHT) + (FRAME_HEIGHT - 1)); | ||||||
|  |     canvas_draw_dot(canvas, scrollbar ? 121 : 126, Y_OFFSET + idx * FRAME_HEIGHT); | ||||||
|  |     canvas_draw_dot( | ||||||
|  |         canvas, scrollbar ? 121 : 126, (Y_OFFSET + idx * FRAME_HEIGHT) + (FRAME_HEIGHT - 1)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void browser_draw_loading(Canvas* canvas, FileBrowserModel* model) { | ||||||
|  |     uint8_t width = 49; | ||||||
|  |     uint8_t height = 47; | ||||||
|  |     uint8_t x = 128 / 2 - width / 2; | ||||||
|  |     uint8_t y = 64 / 2 - height / 2; | ||||||
|  | 
 | ||||||
|  |     UNUSED(model); | ||||||
|  | 
 | ||||||
|  |     elements_bold_rounded_frame(canvas, x, y, width, height); | ||||||
|  | 
 | ||||||
|  |     canvas_set_font(canvas, FontSecondary); | ||||||
|  |     elements_multiline_text(canvas, x + 7, y + 13, "Loading..."); | ||||||
|  | 
 | ||||||
|  |     canvas_draw_icon(canvas, x + 13, y + 19, &A_Loading_24); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { | ||||||
|  |     uint32_t array_size = items_array_size(model->items); | ||||||
|  |     bool show_scrollbar = model->item_cnt > LIST_ITEMS; | ||||||
|  | 
 | ||||||
|  |     string_t filename; | ||||||
|  |     string_init(filename); | ||||||
|  | 
 | ||||||
|  |     for(uint32_t i = 0; i < MIN(model->item_cnt, LIST_ITEMS); i++) { | ||||||
|  |         int32_t idx = CLAMP((uint32_t)(i + model->list_offset), model->item_cnt, 0u); | ||||||
|  | 
 | ||||||
|  |         BrowserItemType item_type = BrowserItemTypeLoading; | ||||||
|  | 
 | ||||||
|  |         if(browser_is_item_in_array(model, idx)) { | ||||||
|  |             BrowserItem_t* item = items_array_get( | ||||||
|  |                 model->items, CLAMP(idx - model->array_offset, (int32_t)(array_size - 1), 0)); | ||||||
|  |             item_type = item->type; | ||||||
|  |             path_extract_filename( | ||||||
|  |                 item->path, filename, (model->hide_ext) && (item_type == BrowserItemTypeFile)); | ||||||
|  |         } else { | ||||||
|  |             string_set_str(filename, "---"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(item_type == BrowserItemTypeBack) { | ||||||
|  |             string_set_str(filename, ". ."); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         elements_string_fit_width( | ||||||
|  |             canvas, filename, (show_scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX)); | ||||||
|  | 
 | ||||||
|  |         if(model->item_idx == idx) { | ||||||
|  |             browser_draw_frame(canvas, i, show_scrollbar); | ||||||
|  |         } else { | ||||||
|  |             canvas_set_color(canvas, ColorBlack); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if((item_type == BrowserItemTypeFile) && (model->file_icon)) { | ||||||
|  |             canvas_draw_icon(canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, model->file_icon); | ||||||
|  |         } else if(BrowserItemIcons[item_type] != NULL) { | ||||||
|  |             canvas_draw_icon( | ||||||
|  |                 canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, BrowserItemIcons[item_type]); | ||||||
|  |         } | ||||||
|  |         canvas_draw_str(canvas, 15, Y_OFFSET + 9 + i * FRAME_HEIGHT, string_get_cstr(filename)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(show_scrollbar) { | ||||||
|  |         elements_scrollbar_pos( | ||||||
|  |             canvas, | ||||||
|  |             126, | ||||||
|  |             Y_OFFSET, | ||||||
|  |             canvas_height(canvas) - Y_OFFSET, | ||||||
|  |             model->item_idx, | ||||||
|  |             model->item_cnt); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(filename); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void file_browser_view_draw_callback(Canvas* canvas, void* _model) { | ||||||
|  |     FileBrowserModel* model = _model; | ||||||
|  | 
 | ||||||
|  |     if(model->folder_loading) { | ||||||
|  |         browser_draw_loading(canvas, model); | ||||||
|  |     } else { | ||||||
|  |         browser_draw_list(canvas, model); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool file_browser_view_input_callback(InputEvent* event, void* context) { | ||||||
|  |     FileBrowser* browser = context; | ||||||
|  |     furi_assert(browser); | ||||||
|  |     bool consumed = false; | ||||||
|  |     bool is_loading = false; | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (FileBrowserModel * model) { | ||||||
|  |             is_loading = model->folder_loading; | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     if(is_loading) { | ||||||
|  |         return false; | ||||||
|  |     } else if(event->key == InputKeyUp || event->key == InputKeyDown) { | ||||||
|  |         if(event->type == InputTypeShort || event->type == InputTypeRepeat) { | ||||||
|  |             with_view_model( | ||||||
|  |                 browser->view, (FileBrowserModel * model) { | ||||||
|  |                     if(event->key == InputKeyUp) { | ||||||
|  |                         model->item_idx = | ||||||
|  |                             ((model->item_idx - 1) + model->item_cnt) % model->item_cnt; | ||||||
|  |                         if(browser_is_list_load_required(model)) { | ||||||
|  |                             model->list_loading = true; | ||||||
|  |                             int32_t load_offset = CLAMP( | ||||||
|  |                                 model->item_idx - ITEM_LIST_LEN_MAX / 4 * 3, | ||||||
|  |                                 (int32_t)model->item_cnt - ITEM_LIST_LEN_MAX, | ||||||
|  |                                 0); | ||||||
|  |                             file_browser_worker_load( | ||||||
|  |                                 browser->worker, load_offset, ITEM_LIST_LEN_MAX); | ||||||
|  |                         } | ||||||
|  |                     } else if(event->key == InputKeyDown) { | ||||||
|  |                         model->item_idx = (model->item_idx + 1) % model->item_cnt; | ||||||
|  |                         if(browser_is_list_load_required(model)) { | ||||||
|  |                             model->list_loading = true; | ||||||
|  |                             int32_t load_offset = CLAMP( | ||||||
|  |                                 model->item_idx - ITEM_LIST_LEN_MAX / 4 * 1, | ||||||
|  |                                 (int32_t)model->item_cnt - ITEM_LIST_LEN_MAX, | ||||||
|  |                                 0); | ||||||
|  |                             file_browser_worker_load( | ||||||
|  |                                 browser->worker, load_offset, ITEM_LIST_LEN_MAX); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     return true; | ||||||
|  |                 }); | ||||||
|  |             browser_update_offset(browser); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } else if(event->key == InputKeyOk) { | ||||||
|  |         if(event->type == InputTypeShort) { | ||||||
|  |             BrowserItem_t* selected_item = NULL; | ||||||
|  |             int32_t select_index = 0; | ||||||
|  |             with_view_model( | ||||||
|  |                 browser->view, (FileBrowserModel * model) { | ||||||
|  |                     if(browser_is_item_in_array(model, model->item_idx)) { | ||||||
|  |                         selected_item = | ||||||
|  |                             items_array_get(model->items, model->item_idx - model->array_offset); | ||||||
|  |                         select_index = model->item_idx; | ||||||
|  |                         if((!model->is_root) && (select_index > 0)) { | ||||||
|  |                             select_index -= 1; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     return false; | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             if(selected_item) { | ||||||
|  |                 if(selected_item->type == BrowserItemTypeBack) { | ||||||
|  |                     file_browser_worker_folder_exit(browser->worker); | ||||||
|  |                 } else if(selected_item->type == BrowserItemTypeFolder) { | ||||||
|  |                     file_browser_worker_folder_enter( | ||||||
|  |                         browser->worker, selected_item->path, select_index); | ||||||
|  |                 } else if(selected_item->type == BrowserItemTypeFile) { | ||||||
|  |                     string_set(browser->result_path, selected_item->path); | ||||||
|  |                     if(browser->callback) { | ||||||
|  |                         browser->callback(browser->context); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } else if(event->key == InputKeyLeft) { | ||||||
|  |         if(event->type == InputTypeShort) { | ||||||
|  |             bool is_root = false; | ||||||
|  |             with_view_model( | ||||||
|  |                 browser->view, (FileBrowserModel * model) { | ||||||
|  |                     is_root = model->is_root; | ||||||
|  |                     return false; | ||||||
|  |                 }); | ||||||
|  |             if(!is_root) { | ||||||
|  |                 file_browser_worker_folder_exit(browser->worker); | ||||||
|  |             } | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								applications/gui/modules/file_browser.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								applications/gui/modules/file_browser.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | |||||||
|  | /**
 | ||||||
|  |  * @file file_browser.h | ||||||
|  |  * GUI: FileBrowser view module API | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "m-string.h" | ||||||
|  | #include <gui/view.h> | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | typedef struct FileBrowser FileBrowser; | ||||||
|  | typedef void (*FileBrowserCallback)(void* context); | ||||||
|  | 
 | ||||||
|  | FileBrowser* file_browser_alloc(string_ptr result_path); | ||||||
|  | 
 | ||||||
|  | void file_browser_free(FileBrowser* browser); | ||||||
|  | 
 | ||||||
|  | View* file_browser_get_view(FileBrowser* browser); | ||||||
|  | 
 | ||||||
|  | void file_browser_configure( | ||||||
|  |     FileBrowser* browser, | ||||||
|  |     const char* extension, | ||||||
|  |     bool skip_assets, | ||||||
|  |     const Icon* file_icon, | ||||||
|  |     bool hide_ext); | ||||||
|  | 
 | ||||||
|  | void file_browser_start(FileBrowser* browser, string_t path); | ||||||
|  | 
 | ||||||
|  | void file_browser_stop(FileBrowser* browser); | ||||||
|  | 
 | ||||||
|  | void file_browser_set_callback(FileBrowser* browser, FileBrowserCallback callback, void* context); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
							
								
								
									
										408
									
								
								applications/gui/modules/file_browser_worker.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										408
									
								
								applications/gui/modules/file_browser_worker.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,408 @@ | |||||||
|  | #include "file_browser_worker.h" | ||||||
|  | #include "furi/check.h" | ||||||
|  | #include "furi/common_defines.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | #include "storage/filesystem_api_defines.h" | ||||||
|  | #include <m-array.h> | ||||||
|  | #include <stdbool.h> | ||||||
|  | #include <storage/storage.h> | ||||||
|  | #include <furi.h> | ||||||
|  | #include <stddef.h> | ||||||
|  | #include "toolbox/path.h" | ||||||
|  | 
 | ||||||
|  | #define TAG "BrowserWorker" | ||||||
|  | 
 | ||||||
|  | #define ASSETS_DIR "assets" | ||||||
|  | #define BROWSER_ROOT "/any" | ||||||
|  | #define FILE_NAME_LEN_MAX 256 | ||||||
|  | #define LONG_LOAD_THRESHOLD 100 | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     WorkerEvtStop = (1 << 0), | ||||||
|  |     WorkerEvtLoad = (1 << 1), | ||||||
|  |     WorkerEvtFolderEnter = (1 << 2), | ||||||
|  |     WorkerEvtFolderExit = (1 << 3), | ||||||
|  | } WorkerEvtFlags; | ||||||
|  | 
 | ||||||
|  | #define WORKER_FLAGS_ALL \ | ||||||
|  |     (WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit) | ||||||
|  | 
 | ||||||
|  | ARRAY_DEF(idx_last_array, int32_t) | ||||||
|  | 
 | ||||||
|  | struct BrowserWorker { | ||||||
|  |     FuriThread* thread; | ||||||
|  | 
 | ||||||
|  |     string_t filter_extension; | ||||||
|  |     string_t path_next; | ||||||
|  |     int32_t item_sel_idx; | ||||||
|  |     uint32_t load_offset; | ||||||
|  |     uint32_t load_count; | ||||||
|  |     bool skip_assets; | ||||||
|  |     idx_last_array_t idx_last; | ||||||
|  | 
 | ||||||
|  |     void* cb_ctx; | ||||||
|  |     BrowserWorkerFolderOpenCallback folder_cb; | ||||||
|  |     BrowserWorkerListLoadCallback list_load_cb; | ||||||
|  |     BrowserWorkerListItemCallback list_item_cb; | ||||||
|  |     BrowserWorkerLongLoadCallback long_load_cb; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static bool browser_path_is_file(string_t path) { | ||||||
|  |     bool state = false; | ||||||
|  |     FileInfo file_info; | ||||||
|  |     Storage* storage = furi_record_open("storage"); | ||||||
|  |     if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) { | ||||||
|  |         if((file_info.flags & FSF_DIRECTORY) == 0) { | ||||||
|  |             state = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     furi_record_close("storage"); | ||||||
|  |     return state; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool browser_path_trim(string_t path) { | ||||||
|  |     bool is_root = false; | ||||||
|  |     size_t filename_start = string_search_rchar(path, '/'); | ||||||
|  |     string_left(path, filename_start); | ||||||
|  |     if((string_empty_p(path)) || (filename_start == STRING_FAILURE)) { | ||||||
|  |         string_set_str(path, BROWSER_ROOT); | ||||||
|  |         is_root = true; | ||||||
|  |     } | ||||||
|  |     return is_root; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool browser_filter_by_name(BrowserWorker* browser, string_t name, bool is_folder) { | ||||||
|  |     if(is_folder) { | ||||||
|  |         // Skip assets folders (if enabled)
 | ||||||
|  |         if(browser->skip_assets) { | ||||||
|  |             return ((string_cmp_str(name, ASSETS_DIR) == 0) ? (false) : (true)); | ||||||
|  |         } else { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         // Filter files by extension
 | ||||||
|  |         if((string_empty_p(browser->filter_extension)) || | ||||||
|  |            (string_cmp_str(browser->filter_extension, "*") == 0)) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |         if(string_end_with_string_p(name, browser->filter_extension)) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool browser_folder_check_and_switch(string_t path) { | ||||||
|  |     FileInfo file_info; | ||||||
|  |     Storage* storage = furi_record_open("storage"); | ||||||
|  |     bool is_root = false; | ||||||
|  |     while(1) { | ||||||
|  |         // Check if folder is existing and navigate back if not
 | ||||||
|  |         if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) { | ||||||
|  |             if(file_info.flags & FSF_DIRECTORY) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if(is_root) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         is_root = browser_path_trim(path); | ||||||
|  |     } | ||||||
|  |     furi_record_close("storage"); | ||||||
|  |     return is_root; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool browser_folder_init( | ||||||
|  |     BrowserWorker* browser, | ||||||
|  |     string_t path, | ||||||
|  |     string_t filename, | ||||||
|  |     uint32_t* item_cnt, | ||||||
|  |     int32_t* file_idx) { | ||||||
|  |     bool state = false; | ||||||
|  |     FileInfo file_info; | ||||||
|  |     uint32_t total_files_cnt = 0; | ||||||
|  | 
 | ||||||
|  |     Storage* storage = furi_record_open("storage"); | ||||||
|  |     File* directory = storage_file_alloc(storage); | ||||||
|  | 
 | ||||||
|  |     char name_temp[FILE_NAME_LEN_MAX]; | ||||||
|  |     string_t name_str; | ||||||
|  |     string_init(name_str); | ||||||
|  | 
 | ||||||
|  |     *item_cnt = 0; | ||||||
|  |     *file_idx = -1; | ||||||
|  | 
 | ||||||
|  |     if(storage_dir_open(directory, string_get_cstr(path))) { | ||||||
|  |         state = true; | ||||||
|  |         while(1) { | ||||||
|  |             if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if((storage_file_get_error(directory) == FSE_OK) && (name_temp[0] != '\0')) { | ||||||
|  |                 total_files_cnt++; | ||||||
|  |                 string_set_str(name_str, name_temp); | ||||||
|  |                 if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { | ||||||
|  |                     if(!string_empty_p(filename)) { | ||||||
|  |                         if(string_cmp(name_str, filename) == 0) { | ||||||
|  |                             *file_idx = *item_cnt; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     (*item_cnt)++; | ||||||
|  |                 } | ||||||
|  |                 if(total_files_cnt == LONG_LOAD_THRESHOLD) { | ||||||
|  |                     // There are too many files in folder and counting them will take some time - send callback to app
 | ||||||
|  |                     if(browser->long_load_cb) { | ||||||
|  |                         browser->long_load_cb(browser->cb_ctx); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(name_str); | ||||||
|  | 
 | ||||||
|  |     storage_dir_close(directory); | ||||||
|  |     storage_file_free(directory); | ||||||
|  | 
 | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | 
 | ||||||
|  |     return state; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool | ||||||
|  |     browser_folder_load(BrowserWorker* browser, string_t path, uint32_t offset, uint32_t count) { | ||||||
|  |     FileInfo file_info; | ||||||
|  | 
 | ||||||
|  |     Storage* storage = furi_record_open("storage"); | ||||||
|  |     File* directory = storage_file_alloc(storage); | ||||||
|  | 
 | ||||||
|  |     char name_temp[FILE_NAME_LEN_MAX]; | ||||||
|  |     string_t name_str; | ||||||
|  |     string_init(name_str); | ||||||
|  | 
 | ||||||
|  |     uint32_t items_cnt = 0; | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         if(!storage_dir_open(directory, string_get_cstr(path))) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         items_cnt = 0; | ||||||
|  |         while(items_cnt < offset) { | ||||||
|  |             if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if(storage_file_get_error(directory) == FSE_OK) { | ||||||
|  |                 string_set_str(name_str, name_temp); | ||||||
|  |                 if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { | ||||||
|  |                     items_cnt++; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if(items_cnt != offset) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(browser->list_load_cb) { | ||||||
|  |             browser->list_load_cb(browser->cb_ctx, offset); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         items_cnt = 0; | ||||||
|  |         while(items_cnt < count) { | ||||||
|  |             if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if(storage_file_get_error(directory) == FSE_OK) { | ||||||
|  |                 string_set_str(name_str, name_temp); | ||||||
|  |                 if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { | ||||||
|  |                     string_printf(name_str, "%s/%s", string_get_cstr(path), name_temp); | ||||||
|  |                     if(browser->list_item_cb) { | ||||||
|  |                         browser->list_item_cb( | ||||||
|  |                             browser->cb_ctx, name_str, (file_info.flags & FSF_DIRECTORY), false); | ||||||
|  |                     } | ||||||
|  |                     items_cnt++; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if(browser->list_item_cb) { | ||||||
|  |             browser->list_item_cb(browser->cb_ctx, NULL, false, true); | ||||||
|  |         } | ||||||
|  |     } while(0); | ||||||
|  | 
 | ||||||
|  |     string_clear(name_str); | ||||||
|  | 
 | ||||||
|  |     storage_dir_close(directory); | ||||||
|  |     storage_file_free(directory); | ||||||
|  | 
 | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | 
 | ||||||
|  |     return (items_cnt == count); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int32_t browser_worker(void* context) { | ||||||
|  |     BrowserWorker* browser = (BrowserWorker*)context; | ||||||
|  |     furi_assert(browser); | ||||||
|  |     FURI_LOG_D(TAG, "Start"); | ||||||
|  | 
 | ||||||
|  |     uint32_t items_cnt = 0; | ||||||
|  |     string_t path; | ||||||
|  |     string_init_set_str(path, BROWSER_ROOT); | ||||||
|  |     browser->item_sel_idx = -1; | ||||||
|  | 
 | ||||||
|  |     // If start path is a path to the file - try finding index of this file in a folder
 | ||||||
|  |     string_t filename; | ||||||
|  |     string_init(filename); | ||||||
|  |     if(browser_path_is_file(browser->path_next)) { | ||||||
|  |         path_extract_filename(browser->path_next, filename, false); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderEnter); | ||||||
|  | 
 | ||||||
|  |     while(1) { | ||||||
|  |         uint32_t flags = osThreadFlagsWait(WORKER_FLAGS_ALL, osFlagsWaitAny, osWaitForever); | ||||||
|  |         furi_assert((flags & osFlagsError) == 0); | ||||||
|  | 
 | ||||||
|  |         if(flags & WorkerEvtFolderEnter) { | ||||||
|  |             string_set(path, browser->path_next); | ||||||
|  |             bool is_root = browser_folder_check_and_switch(path); | ||||||
|  | 
 | ||||||
|  |             // Push previous selected item index to history array
 | ||||||
|  |             idx_last_array_push_back(browser->idx_last, browser->item_sel_idx); | ||||||
|  | 
 | ||||||
|  |             int32_t file_idx = 0; | ||||||
|  |             browser_folder_init(browser, path, filename, &items_cnt, &file_idx); | ||||||
|  |             FURI_LOG_D( | ||||||
|  |                 TAG, | ||||||
|  |                 "Enter folder: %s items: %u idx: %d", | ||||||
|  |                 string_get_cstr(path), | ||||||
|  |                 items_cnt, | ||||||
|  |                 file_idx); | ||||||
|  |             if(browser->folder_cb) { | ||||||
|  |                 browser->folder_cb(browser->cb_ctx, items_cnt, file_idx, is_root); | ||||||
|  |             } | ||||||
|  |             string_reset(filename); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(flags & WorkerEvtFolderExit) { | ||||||
|  |             browser_path_trim(path); | ||||||
|  |             bool is_root = browser_folder_check_and_switch(path); | ||||||
|  | 
 | ||||||
|  |             int32_t file_idx = 0; | ||||||
|  |             browser_folder_init(browser, path, filename, &items_cnt, &file_idx); | ||||||
|  |             if(idx_last_array_size(browser->idx_last) > 0) { | ||||||
|  |                 // Pop previous selected item index from history array
 | ||||||
|  |                 idx_last_array_pop_back(&file_idx, browser->idx_last); | ||||||
|  |             } | ||||||
|  |             FURI_LOG_D( | ||||||
|  |                 TAG, "Exit to: %s items: %u idx: %d", string_get_cstr(path), items_cnt, file_idx); | ||||||
|  |             if(browser->folder_cb) { | ||||||
|  |                 browser->folder_cb(browser->cb_ctx, items_cnt, file_idx, is_root); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(flags & WorkerEvtLoad) { | ||||||
|  |             FURI_LOG_D(TAG, "Load offset: %u cnt: %u", browser->load_offset, browser->load_count); | ||||||
|  |             browser_folder_load(browser, path, browser->load_offset, browser->load_count); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(flags & WorkerEvtStop) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(filename); | ||||||
|  |     string_clear(path); | ||||||
|  | 
 | ||||||
|  |     FURI_LOG_D(TAG, "End"); | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BrowserWorker* file_browser_worker_alloc(string_t path, const char* filter_ext, bool skip_assets) { | ||||||
|  |     BrowserWorker* browser = malloc(sizeof(BrowserWorker)); | ||||||
|  | 
 | ||||||
|  |     idx_last_array_init(browser->idx_last); | ||||||
|  | 
 | ||||||
|  |     string_init_set_str(browser->filter_extension, filter_ext); | ||||||
|  |     browser->skip_assets = skip_assets; | ||||||
|  |     string_init_set(browser->path_next, path); | ||||||
|  | 
 | ||||||
|  |     browser->thread = furi_thread_alloc(); | ||||||
|  |     furi_thread_set_name(browser->thread, "BrowserWorker"); | ||||||
|  |     furi_thread_set_stack_size(browser->thread, 2048); | ||||||
|  |     furi_thread_set_context(browser->thread, browser); | ||||||
|  |     furi_thread_set_callback(browser->thread, browser_worker); | ||||||
|  |     furi_thread_start(browser->thread); | ||||||
|  | 
 | ||||||
|  |     return browser; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_free(BrowserWorker* browser) { | ||||||
|  |     furi_assert(browser); | ||||||
|  | 
 | ||||||
|  |     osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtStop); | ||||||
|  |     furi_thread_join(browser->thread); | ||||||
|  |     furi_thread_free(browser->thread); | ||||||
|  | 
 | ||||||
|  |     string_clear(browser->filter_extension); | ||||||
|  |     string_clear(browser->path_next); | ||||||
|  | 
 | ||||||
|  |     idx_last_array_clear(browser->idx_last); | ||||||
|  | 
 | ||||||
|  |     free(browser); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_callback_context(BrowserWorker* browser, void* context) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     browser->cb_ctx = context; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_folder_callback( | ||||||
|  |     BrowserWorker* browser, | ||||||
|  |     BrowserWorkerFolderOpenCallback cb) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     browser->folder_cb = cb; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_list_callback( | ||||||
|  |     BrowserWorker* browser, | ||||||
|  |     BrowserWorkerListLoadCallback cb) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     browser->list_load_cb = cb; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_item_callback( | ||||||
|  |     BrowserWorker* browser, | ||||||
|  |     BrowserWorkerListItemCallback cb) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     browser->list_item_cb = cb; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_long_load_callback( | ||||||
|  |     BrowserWorker* browser, | ||||||
|  |     BrowserWorkerLongLoadCallback cb) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     browser->long_load_cb = cb; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_folder_enter(BrowserWorker* browser, string_t path, int32_t item_idx) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     string_set(browser->path_next, path); | ||||||
|  |     browser->item_sel_idx = item_idx; | ||||||
|  |     osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderEnter); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_folder_exit(BrowserWorker* browser) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderExit); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_load(BrowserWorker* browser, uint32_t offset, uint32_t count) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     browser->load_offset = offset; | ||||||
|  |     browser->load_count = count; | ||||||
|  |     osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtLoad); | ||||||
|  | } | ||||||
							
								
								
									
										55
									
								
								applications/gui/modules/file_browser_worker.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								applications/gui/modules/file_browser_worker.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "m-string.h" | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <stdint.h> | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | typedef struct BrowserWorker BrowserWorker; | ||||||
|  | typedef void (*BrowserWorkerFolderOpenCallback)( | ||||||
|  |     void* context, | ||||||
|  |     uint32_t item_cnt, | ||||||
|  |     int32_t file_idx, | ||||||
|  |     bool is_root); | ||||||
|  | typedef void (*BrowserWorkerListLoadCallback)(void* context, uint32_t list_load_offset); | ||||||
|  | typedef void (*BrowserWorkerListItemCallback)( | ||||||
|  |     void* context, | ||||||
|  |     string_t item_path, | ||||||
|  |     bool is_folder, | ||||||
|  |     bool is_last); | ||||||
|  | typedef void (*BrowserWorkerLongLoadCallback)(void* context); | ||||||
|  | 
 | ||||||
|  | BrowserWorker* file_browser_worker_alloc(string_t path, const char* filter_ext, bool skip_assets); | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_free(BrowserWorker* browser); | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_callback_context(BrowserWorker* browser, void* context); | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_folder_callback( | ||||||
|  |     BrowserWorker* browser, | ||||||
|  |     BrowserWorkerFolderOpenCallback cb); | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_list_callback( | ||||||
|  |     BrowserWorker* browser, | ||||||
|  |     BrowserWorkerListLoadCallback cb); | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_item_callback( | ||||||
|  |     BrowserWorker* browser, | ||||||
|  |     BrowserWorkerListItemCallback cb); | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_long_load_callback( | ||||||
|  |     BrowserWorker* browser, | ||||||
|  |     BrowserWorkerLongLoadCallback cb); | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_folder_enter(BrowserWorker* browser, string_t path, int32_t item_idx); | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_folder_exit(BrowserWorker* browser); | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_load(BrowserWorker* browser, uint32_t offset, uint32_t count); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
| @ -1,475 +0,0 @@ | |||||||
| #include "file_select.h" |  | ||||||
| #include <gui/elements.h> |  | ||||||
| #include <m-string.h> |  | ||||||
| #include <storage/storage.h> |  | ||||||
| 
 |  | ||||||
| #define FILENAME_COUNT 4 |  | ||||||
| 
 |  | ||||||
| struct FileSelect { |  | ||||||
|     // public
 |  | ||||||
|     View* view; |  | ||||||
|     Storage* fs_api; |  | ||||||
|     const char* path; |  | ||||||
|     const char* extension; |  | ||||||
| 
 |  | ||||||
|     bool init_completed; |  | ||||||
| 
 |  | ||||||
|     FileSelectCallback callback; |  | ||||||
|     void* context; |  | ||||||
| 
 |  | ||||||
|     char* buffer; |  | ||||||
|     uint8_t buffer_size; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     string_t filename[FILENAME_COUNT]; |  | ||||||
|     uint8_t position; |  | ||||||
| 
 |  | ||||||
|     uint16_t first_file_index; |  | ||||||
|     uint16_t file_count; |  | ||||||
| 
 |  | ||||||
| } FileSelectModel; |  | ||||||
| 
 |  | ||||||
| bool file_select_fill_strings(FileSelect* file_select); |  | ||||||
| bool file_select_fill_count(FileSelect* file_select); |  | ||||||
| static bool file_select_init_inner(FileSelect* file_select); |  | ||||||
| 
 |  | ||||||
| static void file_select_draw_callback(Canvas* canvas, void* _model) { |  | ||||||
|     FileSelectModel* model = _model; |  | ||||||
| 
 |  | ||||||
|     string_t string_buff; |  | ||||||
|     const uint8_t item_height = 16; |  | ||||||
|     const uint8_t item_width = 123; |  | ||||||
|     const uint8_t text_max_width = 115; |  | ||||||
| 
 |  | ||||||
|     canvas_clear(canvas); |  | ||||||
|     canvas_set_font(canvas, FontSecondary); |  | ||||||
| 
 |  | ||||||
|     if(model->file_count) { |  | ||||||
|         for(uint8_t i = 0; i < MIN(FILENAME_COUNT, model->file_count); i++) { |  | ||||||
|             if(i == model->position) { |  | ||||||
|                 canvas_set_color(canvas, ColorBlack); |  | ||||||
|                 canvas_draw_box(canvas, 0, (i * item_height) + 1, item_width, item_height - 2); |  | ||||||
| 
 |  | ||||||
|                 canvas_set_color(canvas, ColorWhite); |  | ||||||
|                 canvas_draw_dot(canvas, 0, (i * item_height) + 1); |  | ||||||
|                 canvas_draw_dot(canvas, 0, (i * item_height) + item_height - 2); |  | ||||||
|                 canvas_draw_dot(canvas, item_width - 1, (i * item_height) + 1); |  | ||||||
|                 canvas_draw_dot(canvas, item_width - 1, (i * item_height) + item_height - 2); |  | ||||||
|             } else { |  | ||||||
|                 canvas_set_color(canvas, ColorBlack); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             string_init_set(string_buff, model->filename[i]); |  | ||||||
|             elements_string_fit_width(canvas, string_buff, text_max_width); |  | ||||||
|             canvas_draw_str( |  | ||||||
|                 canvas, 6, (i * item_height) + item_height - 4, string_get_cstr(string_buff)); |  | ||||||
| 
 |  | ||||||
|             string_clear(string_buff); |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         canvas_draw_str(canvas, 6, item_height, "Empty folder"); |  | ||||||
|     } |  | ||||||
|     elements_scrollbar(canvas, model->first_file_index + model->position, model->file_count); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool file_select_input_callback(InputEvent* event, void* context) { |  | ||||||
|     FileSelect* file_select = (FileSelect*)context; |  | ||||||
|     bool consumed = false; |  | ||||||
| 
 |  | ||||||
|     if((event->type == InputTypeShort) | (event->type == InputTypeRepeat)) { |  | ||||||
|         if(!file_select->init_completed) { |  | ||||||
|             if(!file_select_init_inner(file_select)) { |  | ||||||
|                 file_select->callback(false, file_select->context); |  | ||||||
|             } |  | ||||||
|         } else if(event->key == InputKeyUp) { |  | ||||||
|             with_view_model( |  | ||||||
|                 file_select->view, (FileSelectModel * model) { |  | ||||||
|                     if(model->position == 0) { |  | ||||||
|                         if(model->first_file_index == 0) { |  | ||||||
|                             // wrap
 |  | ||||||
|                             int16_t max_first_file_index = model->file_count - FILENAME_COUNT; |  | ||||||
|                             model->position = MIN(FILENAME_COUNT - 1, model->file_count - 1); |  | ||||||
|                             model->first_file_index = |  | ||||||
|                                 max_first_file_index < 0 ? 0 : max_first_file_index; |  | ||||||
|                         } else { |  | ||||||
|                             model->first_file_index--; |  | ||||||
|                         } |  | ||||||
|                     } else if(model->position == 1) { |  | ||||||
|                         if(model->first_file_index == 0) { |  | ||||||
|                             model->position--; |  | ||||||
|                         } else { |  | ||||||
|                             model->first_file_index--; |  | ||||||
|                         } |  | ||||||
|                     } else { |  | ||||||
|                         model->position--; |  | ||||||
|                     } |  | ||||||
|                     return true; |  | ||||||
|                 }); |  | ||||||
|             consumed = true; |  | ||||||
|             if(!file_select_fill_strings(file_select)) { |  | ||||||
|                 file_select->callback(false, file_select->context); |  | ||||||
|             } |  | ||||||
|         } else if(event->key == InputKeyDown) { |  | ||||||
|             with_view_model( |  | ||||||
|                 file_select->view, (FileSelectModel * model) { |  | ||||||
|                     uint16_t max_first_file_index = model->file_count > FILENAME_COUNT ? |  | ||||||
|                                                         model->file_count - FILENAME_COUNT : |  | ||||||
|                                                         0; |  | ||||||
| 
 |  | ||||||
|                     if(model->position >= MIN(FILENAME_COUNT - 1, model->file_count - 1)) { |  | ||||||
|                         if(model->first_file_index >= max_first_file_index) { |  | ||||||
|                             // wrap
 |  | ||||||
|                             model->position = 0; |  | ||||||
|                             model->first_file_index = 0; |  | ||||||
|                         } else { |  | ||||||
|                             model->first_file_index++; |  | ||||||
|                         } |  | ||||||
|                     } else if(model->position >= (FILENAME_COUNT - 2)) { |  | ||||||
|                         if(model->first_file_index >= max_first_file_index) { |  | ||||||
|                             model->position++; |  | ||||||
|                         } else { |  | ||||||
|                             model->first_file_index++; |  | ||||||
|                         } |  | ||||||
|                     } else { |  | ||||||
|                         model->position++; |  | ||||||
|                     } |  | ||||||
|                     return true; |  | ||||||
|                 }); |  | ||||||
|             consumed = true; |  | ||||||
|             if(!file_select_fill_strings(file_select)) { |  | ||||||
|                 file_select->callback(false, file_select->context); |  | ||||||
|             } |  | ||||||
|         } else if(event->key == InputKeyOk) { |  | ||||||
|             if(file_select->callback != NULL) { |  | ||||||
|                 size_t files = 0; |  | ||||||
|                 if(file_select->buffer) { |  | ||||||
|                     with_view_model( |  | ||||||
|                         file_select->view, (FileSelectModel * model) { |  | ||||||
|                             files = model->file_count; |  | ||||||
|                             strlcpy( |  | ||||||
|                                 file_select->buffer, |  | ||||||
|                                 string_get_cstr(model->filename[model->position]), |  | ||||||
|                                 file_select->buffer_size); |  | ||||||
| 
 |  | ||||||
|                             return false; |  | ||||||
|                         }); |  | ||||||
|                 }; |  | ||||||
|                 if(files > 0) { |  | ||||||
|                     file_select->callback(true, file_select->context); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             consumed = true; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return consumed; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool file_select_init_inner(FileSelect* file_select) { |  | ||||||
|     bool result = false; |  | ||||||
|     if(file_select->path && file_select->extension && file_select->fs_api) { |  | ||||||
|         if(file_select_fill_count(file_select)) { |  | ||||||
|             if(file_select_fill_strings(file_select)) { |  | ||||||
|                 file_select->init_completed = true; |  | ||||||
|                 result = true; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| FileSelect* file_select_alloc() { |  | ||||||
|     FileSelect* file_select = malloc(sizeof(FileSelect)); |  | ||||||
|     file_select->view = view_alloc(); |  | ||||||
|     file_select->fs_api = furi_record_open("storage"); |  | ||||||
| 
 |  | ||||||
|     view_set_context(file_select->view, file_select); |  | ||||||
|     view_allocate_model(file_select->view, ViewModelTypeLockFree, sizeof(FileSelectModel)); |  | ||||||
|     view_set_draw_callback(file_select->view, file_select_draw_callback); |  | ||||||
|     view_set_input_callback(file_select->view, file_select_input_callback); |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         file_select->view, (FileSelectModel * model) { |  | ||||||
|             for(uint8_t i = 0; i < FILENAME_COUNT; i++) { |  | ||||||
|                 string_init(model->filename[i]); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             model->first_file_index = 0; |  | ||||||
|             model->file_count = 0; |  | ||||||
|             return false; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     return file_select; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void file_select_free(FileSelect* file_select) { |  | ||||||
|     furi_assert(file_select); |  | ||||||
|     with_view_model( |  | ||||||
|         file_select->view, (FileSelectModel * model) { |  | ||||||
|             for(uint8_t i = 0; i < FILENAME_COUNT; i++) { |  | ||||||
|                 string_clear(model->filename[i]); |  | ||||||
|             } |  | ||||||
|             return false; |  | ||||||
|         }); |  | ||||||
|     view_free(file_select->view); |  | ||||||
|     free(file_select); |  | ||||||
|     furi_record_close("storage"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| View* file_select_get_view(FileSelect* file_select) { |  | ||||||
|     furi_assert(file_select); |  | ||||||
|     return file_select->view; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context) { |  | ||||||
|     file_select->context = context; |  | ||||||
|     file_select->callback = callback; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void file_select_set_filter(FileSelect* file_select, const char* path, const char* extension) { |  | ||||||
|     furi_assert(file_select); |  | ||||||
|     file_select->path = path; |  | ||||||
|     file_select->extension = extension; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size) { |  | ||||||
|     file_select->buffer = buffer; |  | ||||||
|     file_select->buffer_size = buffer_size; |  | ||||||
| 
 |  | ||||||
|     if(file_select->buffer) { |  | ||||||
|         strlcpy(file_select->buffer, "", file_select->buffer_size); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool file_select_init(FileSelect* file_select) { |  | ||||||
|     if(!file_select_init_inner(file_select)) { |  | ||||||
|         file_select->callback(false, file_select->context); |  | ||||||
|         return false; |  | ||||||
|     } else { |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool filter_file(FileSelect* file_select, FileInfo* file_info, char* name) { |  | ||||||
|     bool result = false; |  | ||||||
| 
 |  | ||||||
|     if(!(file_info->flags & FSF_DIRECTORY)) { |  | ||||||
|         if(strcmp(file_select->extension, "*") == 0) { |  | ||||||
|             result = true; |  | ||||||
|         } else if(strstr(name, file_select->extension) != NULL) { |  | ||||||
|             result = true; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool file_select_fill_strings(FileSelect* file_select) { |  | ||||||
|     furi_assert(file_select); |  | ||||||
|     furi_assert(file_select->fs_api); |  | ||||||
|     furi_assert(file_select->path); |  | ||||||
|     furi_assert(file_select->extension); |  | ||||||
| 
 |  | ||||||
|     FileInfo file_info; |  | ||||||
|     File* directory = storage_file_alloc(file_select->fs_api); |  | ||||||
| 
 |  | ||||||
|     uint8_t string_counter = 0; |  | ||||||
|     uint16_t file_counter = 0; |  | ||||||
|     const uint8_t name_length = 100; |  | ||||||
|     char* name = malloc(name_length); |  | ||||||
|     uint16_t first_file_index = 0; |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         file_select->view, (FileSelectModel * model) { |  | ||||||
|             first_file_index = model->first_file_index; |  | ||||||
|             return false; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     if(!storage_dir_open(directory, file_select->path)) { |  | ||||||
|         storage_dir_close(directory); |  | ||||||
|         storage_file_free(directory); |  | ||||||
|         free(name); |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     while(1) { |  | ||||||
|         if(!storage_dir_read(directory, &file_info, name, name_length)) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if(storage_file_get_error(directory) == FSE_OK) { |  | ||||||
|             if(filter_file(file_select, &file_info, name)) { |  | ||||||
|                 if(file_counter >= first_file_index) { |  | ||||||
|                     with_view_model( |  | ||||||
|                         file_select->view, (FileSelectModel * model) { |  | ||||||
|                             string_set_str(model->filename[string_counter], name); |  | ||||||
| 
 |  | ||||||
|                             if(strcmp(file_select->extension, "*") != 0) { |  | ||||||
|                                 string_replace_all_str( |  | ||||||
|                                     model->filename[string_counter], file_select->extension, ""); |  | ||||||
|                             } |  | ||||||
| 
 |  | ||||||
|                             return true; |  | ||||||
|                         }); |  | ||||||
|                     string_counter++; |  | ||||||
| 
 |  | ||||||
|                     if(string_counter >= FILENAME_COUNT) { |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 file_counter++; |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             storage_dir_close(directory); |  | ||||||
|             storage_file_free(directory); |  | ||||||
|             free(name); |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     storage_dir_close(directory); |  | ||||||
|     storage_file_free(directory); |  | ||||||
|     free(name); |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool file_select_fill_count(FileSelect* file_select) { |  | ||||||
|     furi_assert(file_select); |  | ||||||
|     furi_assert(file_select->fs_api); |  | ||||||
|     furi_assert(file_select->path); |  | ||||||
|     furi_assert(file_select->extension); |  | ||||||
| 
 |  | ||||||
|     FileInfo file_info; |  | ||||||
|     File* directory = storage_file_alloc(file_select->fs_api); |  | ||||||
| 
 |  | ||||||
|     uint16_t file_counter = 0; |  | ||||||
|     const uint8_t name_length = 100; |  | ||||||
|     char* name = malloc(name_length); |  | ||||||
| 
 |  | ||||||
|     if(!storage_dir_open(directory, file_select->path)) { |  | ||||||
|         storage_dir_close(directory); |  | ||||||
|         storage_file_free(directory); |  | ||||||
|         free(name); |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     while(1) { |  | ||||||
|         if(!storage_dir_read(directory, &file_info, name, name_length)) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if(storage_file_get_error(directory) == FSE_OK) { |  | ||||||
|             if(filter_file(file_select, &file_info, name)) { |  | ||||||
|                 file_counter++; |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             storage_dir_close(directory); |  | ||||||
|             storage_file_free(directory); |  | ||||||
|             free(name); |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         file_select->view, (FileSelectModel * model) { |  | ||||||
|             model->file_count = file_counter; |  | ||||||
|             return false; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     storage_dir_close(directory); |  | ||||||
|     storage_file_free(directory); |  | ||||||
|     free(name); |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void file_select_set_selected_file_internal(FileSelect* file_select, const char* filename) { |  | ||||||
|     furi_assert(file_select); |  | ||||||
|     furi_assert(filename); |  | ||||||
|     furi_assert(file_select->fs_api); |  | ||||||
|     furi_assert(file_select->path); |  | ||||||
|     furi_assert(file_select->extension); |  | ||||||
| 
 |  | ||||||
|     if(strlen(filename) == 0) return; |  | ||||||
| 
 |  | ||||||
|     FileInfo file_info; |  | ||||||
|     File* directory = storage_file_alloc(file_select->fs_api); |  | ||||||
| 
 |  | ||||||
|     const uint8_t name_length = 100; |  | ||||||
|     char* name = malloc(name_length); |  | ||||||
|     uint16_t file_position = 0; |  | ||||||
|     bool file_found = false; |  | ||||||
| 
 |  | ||||||
|     string_t filename_str; |  | ||||||
|     string_init_set_str(filename_str, filename); |  | ||||||
|     if(strcmp(file_select->extension, "*") != 0) { |  | ||||||
|         string_cat_str(filename_str, file_select->extension); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(!storage_dir_open(directory, file_select->path)) { |  | ||||||
|         string_clear(filename_str); |  | ||||||
|         storage_dir_close(directory); |  | ||||||
|         storage_file_free(directory); |  | ||||||
|         free(name); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     while(1) { |  | ||||||
|         if(!storage_dir_read(directory, &file_info, name, name_length)) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if(storage_file_get_error(directory) == FSE_OK) { |  | ||||||
|             if(filter_file(file_select, &file_info, name)) { |  | ||||||
|                 if(strcmp(string_get_cstr(filename_str), name) == 0) { |  | ||||||
|                     file_found = true; |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 file_position++; |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             string_clear(filename_str); |  | ||||||
|             storage_dir_close(directory); |  | ||||||
|             storage_file_free(directory); |  | ||||||
|             free(name); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(file_found) { |  | ||||||
|         with_view_model( |  | ||||||
|             file_select->view, (FileSelectModel * model) { |  | ||||||
|                 uint16_t max_first_file_index = |  | ||||||
|                     model->file_count > FILENAME_COUNT ? model->file_count - FILENAME_COUNT : 0; |  | ||||||
| 
 |  | ||||||
|                 model->first_file_index = file_position; |  | ||||||
| 
 |  | ||||||
|                 if(model->first_file_index > 0) { |  | ||||||
|                     model->first_file_index -= 1; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 if(model->first_file_index >= max_first_file_index) { |  | ||||||
|                     model->first_file_index = max_first_file_index; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 model->position = file_position - model->first_file_index; |  | ||||||
| 
 |  | ||||||
|                 return true; |  | ||||||
|             }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     string_clear(filename_str); |  | ||||||
|     storage_dir_close(directory); |  | ||||||
|     storage_file_free(directory); |  | ||||||
|     free(name); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void file_select_set_selected_file(FileSelect* file_select, const char* filename) { |  | ||||||
|     file_select_set_selected_file_internal(file_select, filename); |  | ||||||
| 
 |  | ||||||
|     if(!file_select_fill_strings(file_select)) { |  | ||||||
|         file_select->callback(false, file_select->context); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,31 +0,0 @@ | |||||||
| /**
 |  | ||||||
|  * @file file_select.h |  | ||||||
|  * GUI: FileSelect view module API |  | ||||||
|  */ |  | ||||||
| 
 |  | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <gui/view.h> |  | ||||||
| 
 |  | ||||||
| #ifdef __cplusplus |  | ||||||
| extern "C" { |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| typedef struct FileSelect FileSelect; |  | ||||||
| 
 |  | ||||||
| typedef void (*FileSelectCallback)(bool result, void* context); |  | ||||||
| 
 |  | ||||||
| FileSelect* file_select_alloc(); |  | ||||||
| 
 |  | ||||||
| void file_select_free(FileSelect* file_select); |  | ||||||
| View* file_select_get_view(FileSelect* file_select); |  | ||||||
| 
 |  | ||||||
| void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context); |  | ||||||
| void file_select_set_filter(FileSelect* file_select, const char* path, const char* extension); |  | ||||||
| void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size); |  | ||||||
| bool file_select_init(FileSelect* file_select); |  | ||||||
| void file_select_set_selected_file(FileSelect* file_select, const char* filename); |  | ||||||
| 
 |  | ||||||
| #ifdef __cplusplus |  | ||||||
| } |  | ||||||
| #endif |  | ||||||
| @ -3,7 +3,7 @@ | |||||||
| #include "applications/storage/storage.h" | #include "applications/storage/storage.h" | ||||||
| 
 | 
 | ||||||
| struct ValidatorIsFile { | struct ValidatorIsFile { | ||||||
|     const char* app_path_folder; |     char* app_path_folder; | ||||||
|     const char* app_extension; |     const char* app_extension; | ||||||
|     char* current_name; |     char* current_name; | ||||||
| }; | }; | ||||||
| @ -40,7 +40,7 @@ ValidatorIsFile* validator_is_file_alloc_init( | |||||||
|     const char* current_name) { |     const char* current_name) { | ||||||
|     ValidatorIsFile* instance = malloc(sizeof(ValidatorIsFile)); |     ValidatorIsFile* instance = malloc(sizeof(ValidatorIsFile)); | ||||||
| 
 | 
 | ||||||
|     instance->app_path_folder = app_path_folder; |     instance->app_path_folder = strdup(app_path_folder); | ||||||
|     instance->app_extension = app_extension; |     instance->app_extension = app_extension; | ||||||
|     instance->current_name = strdup(current_name); |     instance->current_name = strdup(current_name); | ||||||
| 
 | 
 | ||||||
| @ -49,6 +49,7 @@ ValidatorIsFile* validator_is_file_alloc_init( | |||||||
| 
 | 
 | ||||||
| void validator_is_file_free(ValidatorIsFile* instance) { | void validator_is_file_free(ValidatorIsFile* instance) { | ||||||
|     furi_assert(instance); |     furi_assert(instance); | ||||||
|  |     free(instance->app_path_folder); | ||||||
|     free(instance->current_name); |     free(instance->current_name); | ||||||
|     free(instance); |     free(instance); | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,7 +1,8 @@ | |||||||
| #include "ibutton.h" | #include "ibutton.h" | ||||||
|  | #include "assets_icons.h" | ||||||
| #include "ibutton_i.h" | #include "ibutton_i.h" | ||||||
| #include "ibutton/scenes/ibutton_scene.h" | #include "ibutton/scenes/ibutton_scene.h" | ||||||
| 
 | #include "m-string.h" | ||||||
| #include <toolbox/path.h> | #include <toolbox/path.h> | ||||||
| #include <flipper_format/flipper_format.h> | #include <flipper_format/flipper_format.h> | ||||||
| 
 | 
 | ||||||
| @ -85,6 +86,8 @@ void ibutton_tick_event_callback(void* context) { | |||||||
| iButton* ibutton_alloc() { | iButton* ibutton_alloc() { | ||||||
|     iButton* ibutton = malloc(sizeof(iButton)); |     iButton* ibutton = malloc(sizeof(iButton)); | ||||||
| 
 | 
 | ||||||
|  |     string_init(ibutton->file_path); | ||||||
|  | 
 | ||||||
|     ibutton->scene_manager = scene_manager_alloc(&ibutton_scene_handlers, ibutton); |     ibutton->scene_manager = scene_manager_alloc(&ibutton_scene_handlers, ibutton); | ||||||
| 
 | 
 | ||||||
|     ibutton->view_dispatcher = view_dispatcher_alloc(); |     ibutton->view_dispatcher = view_dispatcher_alloc(); | ||||||
| @ -176,49 +179,28 @@ void ibutton_free(iButton* ibutton) { | |||||||
|     ibutton_worker_free(ibutton->key_worker); |     ibutton_worker_free(ibutton->key_worker); | ||||||
|     ibutton_key_free(ibutton->key); |     ibutton_key_free(ibutton->key); | ||||||
| 
 | 
 | ||||||
|  |     string_clear(ibutton->file_path); | ||||||
|  | 
 | ||||||
|     free(ibutton); |     free(ibutton); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ibutton_file_select(iButton* ibutton) { | bool ibutton_file_select(iButton* ibutton) { | ||||||
|     bool success = dialog_file_select_show( |     bool success = dialog_file_browser_show( | ||||||
|         ibutton->dialogs, |         ibutton->dialogs, | ||||||
|         IBUTTON_APP_FOLDER, |         ibutton->file_path, | ||||||
|  |         ibutton->file_path, | ||||||
|         IBUTTON_APP_EXTENSION, |         IBUTTON_APP_EXTENSION, | ||||||
|         ibutton->file_name, |         true, | ||||||
|         IBUTTON_FILE_NAME_SIZE, |         &I_ibutt_10px, | ||||||
|         ibutton_key_get_name_p(ibutton->key)); |         true); | ||||||
| 
 | 
 | ||||||
|     if(success) { |     if(success) { | ||||||
|         string_t key_str; |         success = ibutton_load_key_data(ibutton, ibutton->file_path); | ||||||
|         string_init_printf( |  | ||||||
|             key_str, "%s/%s%s", IBUTTON_APP_FOLDER, ibutton->file_name, IBUTTON_APP_EXTENSION); |  | ||||||
|         success = ibutton_load_key_data(ibutton, key_str); |  | ||||||
| 
 |  | ||||||
|         if(success) { |  | ||||||
|             ibutton_key_set_name(ibutton->key, ibutton->file_name); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         string_clear(key_str); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return success; |     return success; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ibutton_load_key(iButton* ibutton, const char* key_name) { |  | ||||||
|     string_t key_path; |  | ||||||
|     string_init_set_str(key_path, key_name); |  | ||||||
| 
 |  | ||||||
|     const bool success = ibutton_load_key_data(ibutton, key_path); |  | ||||||
| 
 |  | ||||||
|     if(success) { |  | ||||||
|         path_extract_filename_no_ext(key_name, key_path); |  | ||||||
|         ibutton_key_set_name(ibutton->key, string_get_cstr(key_path)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     string_clear(key_path); |  | ||||||
|     return success; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool ibutton_save_key(iButton* ibutton, const char* key_name) { | bool ibutton_save_key(iButton* ibutton, const char* key_name) { | ||||||
|     // Create ibutton directory if necessary
 |     // Create ibutton directory if necessary
 | ||||||
|     ibutton_make_app_folder(ibutton); |     ibutton_make_app_folder(ibutton); | ||||||
| @ -226,27 +208,23 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { | |||||||
|     FlipperFormat* file = flipper_format_file_alloc(ibutton->storage); |     FlipperFormat* file = flipper_format_file_alloc(ibutton->storage); | ||||||
|     iButtonKey* key = ibutton->key; |     iButtonKey* key = ibutton->key; | ||||||
| 
 | 
 | ||||||
|     string_t key_file_name; |  | ||||||
|     bool result = false; |     bool result = false; | ||||||
|     string_init(key_file_name); |  | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         // First remove key if it was saved (we rename the key)
 |         // Check if we has old key
 | ||||||
|         if(!ibutton_delete_key(ibutton)) break; |         if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { | ||||||
|  |             // First remove old key
 | ||||||
|  |             ibutton_delete_key(ibutton); | ||||||
| 
 | 
 | ||||||
|         // Save the key
 |             // Remove old key name from path
 | ||||||
|         ibutton_key_set_name(key, key_name); |             size_t filename_start = string_search_rchar(ibutton->file_path, '/'); | ||||||
|  |             string_left(ibutton->file_path, filename_start); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         // Set full file name, for new key
 |         string_cat_printf(ibutton->file_path, "/%s%s", key_name, IBUTTON_APP_EXTENSION); | ||||||
|         string_printf( |  | ||||||
|             key_file_name, |  | ||||||
|             "%s/%s%s", |  | ||||||
|             IBUTTON_APP_FOLDER, |  | ||||||
|             ibutton_key_get_name_p(key), |  | ||||||
|             IBUTTON_APP_EXTENSION); |  | ||||||
| 
 | 
 | ||||||
|         // Open file for write
 |         // Open file for write
 | ||||||
|         if(!flipper_format_file_open_always(file, string_get_cstr(key_file_name))) break; |         if(!flipper_format_file_open_always(file, string_get_cstr(ibutton->file_path))) break; | ||||||
| 
 | 
 | ||||||
|         // Write header
 |         // Write header
 | ||||||
|         if(!flipper_format_write_header_cstr(file, IBUTTON_APP_FILE_TYPE, 1)) break; |         if(!flipper_format_write_header_cstr(file, IBUTTON_APP_FILE_TYPE, 1)) break; | ||||||
| @ -271,8 +249,6 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { | |||||||
| 
 | 
 | ||||||
|     flipper_format_free(file); |     flipper_format_free(file); | ||||||
| 
 | 
 | ||||||
|     string_clear(key_file_name); |  | ||||||
| 
 |  | ||||||
|     if(!result) { |     if(!result) { | ||||||
|         dialog_message_show_storage_error(ibutton->dialogs, "Cannot save\nkey file"); |         dialog_message_show_storage_error(ibutton->dialogs, "Cannot save\nkey file"); | ||||||
|     } |     } | ||||||
| @ -281,17 +257,8 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ibutton_delete_key(iButton* ibutton) { | bool ibutton_delete_key(iButton* ibutton) { | ||||||
|     string_t file_name; |  | ||||||
|     bool result = false; |     bool result = false; | ||||||
| 
 |     result = storage_simply_remove(ibutton->storage, string_get_cstr(ibutton->file_path)); | ||||||
|     string_init_printf( |  | ||||||
|         file_name, |  | ||||||
|         "%s/%s%s", |  | ||||||
|         IBUTTON_APP_FOLDER, |  | ||||||
|         ibutton_key_get_name_p(ibutton->key), |  | ||||||
|         IBUTTON_APP_EXTENSION); |  | ||||||
|     result = storage_simply_remove(ibutton->storage, string_get_cstr(file_name)); |  | ||||||
|     string_clear(file_name); |  | ||||||
| 
 | 
 | ||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| @ -335,8 +302,17 @@ int32_t ibutton_app(void* p) { | |||||||
| 
 | 
 | ||||||
|     ibutton_make_app_folder(ibutton); |     ibutton_make_app_folder(ibutton); | ||||||
| 
 | 
 | ||||||
|     if(p && ibutton_load_key(ibutton, (const char*)p)) { |     bool key_loaded = false; | ||||||
|         // TODO: Display an error if the key from p could not be loaded
 | 
 | ||||||
|  |     if(p) { | ||||||
|  |         string_set_str(ibutton->file_path, (const char*)p); | ||||||
|  |         if(ibutton_load_key_data(ibutton, ibutton->file_path)) { | ||||||
|  |             key_loaded = true; | ||||||
|  |             // TODO: Display an error if the key from p could not be loaded
 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(key_loaded) { | ||||||
|         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate); |         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate); | ||||||
|     } else { |     } else { | ||||||
|         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneStart); |         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneStart); | ||||||
|  | |||||||
| @ -41,7 +41,7 @@ struct iButton { | |||||||
|     iButtonWorker* key_worker; |     iButtonWorker* key_worker; | ||||||
|     iButtonKey* key; |     iButtonKey* key; | ||||||
| 
 | 
 | ||||||
|     char file_name[IBUTTON_FILE_NAME_SIZE]; |     string_t file_path; | ||||||
|     char text_store[IBUTTON_TEXT_STORE_SIZE + 1]; |     char text_store[IBUTTON_TEXT_STORE_SIZE + 1]; | ||||||
| 
 | 
 | ||||||
|     Submenu* submenu; |     Submenu* submenu; | ||||||
| @ -74,7 +74,6 @@ typedef enum { | |||||||
| } iButtonNotificationMessage; | } iButtonNotificationMessage; | ||||||
| 
 | 
 | ||||||
| bool ibutton_file_select(iButton* ibutton); | bool ibutton_file_select(iButton* ibutton); | ||||||
| bool ibutton_load_key(iButton* ibutton, const char* key_name); |  | ||||||
| bool ibutton_save_key(iButton* ibutton, const char* key_name); | bool ibutton_save_key(iButton* ibutton, const char* key_name); | ||||||
| bool ibutton_delete_key(iButton* ibutton); | bool ibutton_delete_key(iButton* ibutton); | ||||||
| void ibutton_text_store_set(iButton* ibutton, const char* text, ...); | void ibutton_text_store_set(iButton* ibutton, const char* text, ...); | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "../ibutton_i.h" | #include "../ibutton_i.h" | ||||||
|  | #include "m-string.h" | ||||||
| 
 | 
 | ||||||
| enum SubmenuIndex { | enum SubmenuIndex { | ||||||
|     SubmenuIndexCyfral, |     SubmenuIndexCyfral, | ||||||
| @ -44,7 +45,7 @@ bool ibutton_scene_add_type_on_event(void* context, SceneManagerEvent event) { | |||||||
|             furi_crash("Unknown key type"); |             furi_crash("Unknown key type"); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         ibutton_key_set_name(key, ""); |         string_set_str(ibutton->file_path, IBUTTON_APP_FOLDER); | ||||||
|         ibutton_key_clear_data(key); |         ibutton_key_clear_data(key); | ||||||
|         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddValue); |         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddValue); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "../ibutton_i.h" | #include "../ibutton_i.h" | ||||||
|  | #include <toolbox/path.h> | ||||||
| 
 | 
 | ||||||
| static void ibutton_scene_delete_confirm_widget_callback( | static void ibutton_scene_delete_confirm_widget_callback( | ||||||
|     GuiButtonType result, |     GuiButtonType result, | ||||||
| @ -16,7 +17,11 @@ void ibutton_scene_delete_confirm_on_enter(void* context) { | |||||||
|     iButtonKey* key = ibutton->key; |     iButtonKey* key = ibutton->key; | ||||||
|     const uint8_t* key_data = ibutton_key_get_data_p(key); |     const uint8_t* key_data = ibutton_key_get_data_p(key); | ||||||
| 
 | 
 | ||||||
|     ibutton_text_store_set(ibutton, "\e#Delete %s?\e#", ibutton_key_get_name_p(key)); |     string_t key_name; | ||||||
|  |     string_init(key_name); | ||||||
|  |     path_extract_filename(ibutton->file_path, key_name, true); | ||||||
|  | 
 | ||||||
|  |     ibutton_text_store_set(ibutton, "\e#Delete %s?\e#", string_get_cstr(key_name)); | ||||||
|     widget_add_text_box_element( |     widget_add_text_box_element( | ||||||
|         widget, 0, 0, 128, 27, AlignCenter, AlignCenter, ibutton->text_store, false); |         widget, 0, 0, 128, 27, AlignCenter, AlignCenter, ibutton->text_store, false); | ||||||
|     widget_add_button_element( |     widget_add_button_element( | ||||||
| @ -62,6 +67,8 @@ void ibutton_scene_delete_confirm_on_enter(void* context) { | |||||||
|         widget, 64, 33, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store); |         widget, 64, 33, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); |     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); | ||||||
|  | 
 | ||||||
|  |     string_clear(key_name); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) { | bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) { | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| #include "../ibutton_i.h" | #include "../ibutton_i.h" | ||||||
| #include <dolphin/dolphin.h> | #include <dolphin/dolphin.h> | ||||||
|  | #include <toolbox/path.h> | ||||||
| 
 | 
 | ||||||
| static void ibutton_scene_emulate_callback(void* context, bool emulated) { | static void ibutton_scene_emulate_callback(void* context, bool emulated) { | ||||||
|     iButton* ibutton = context; |     iButton* ibutton = context; | ||||||
| @ -15,14 +16,19 @@ void ibutton_scene_emulate_on_enter(void* context) { | |||||||
|     iButtonKey* key = ibutton->key; |     iButtonKey* key = ibutton->key; | ||||||
| 
 | 
 | ||||||
|     const uint8_t* key_data = ibutton_key_get_data_p(key); |     const uint8_t* key_data = ibutton_key_get_data_p(key); | ||||||
|     const char* key_name = ibutton_key_get_name_p(key); | 
 | ||||||
|  |     string_t key_name; | ||||||
|  |     string_init(key_name); | ||||||
|  |     if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { | ||||||
|  |         path_extract_filename(ibutton->file_path, key_name, true); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     uint8_t line_count = 2; |     uint8_t line_count = 2; | ||||||
|     DOLPHIN_DEED(DolphinDeedIbuttonEmulate); |     DOLPHIN_DEED(DolphinDeedIbuttonEmulate); | ||||||
| 
 | 
 | ||||||
|     // check that stored key has name
 |     // check that stored key has name
 | ||||||
|     if(strcmp(key_name, "") != 0) { |     if(!string_empty_p(key_name)) { | ||||||
|         ibutton_text_store_set(ibutton, "emulating\n%s", key_name); |         ibutton_text_store_set(ibutton, "emulating\n%s", string_get_cstr(key_name)); | ||||||
|         line_count = 2; |         line_count = 2; | ||||||
|     } else { |     } else { | ||||||
|         // if not, show key data
 |         // if not, show key data
 | ||||||
| @ -77,6 +83,8 @@ void ibutton_scene_emulate_on_enter(void* context) { | |||||||
|     ibutton_worker_emulate_set_callback( |     ibutton_worker_emulate_set_callback( | ||||||
|         ibutton->key_worker, ibutton_scene_emulate_callback, ibutton); |         ibutton->key_worker, ibutton_scene_emulate_callback, ibutton); | ||||||
|     ibutton_worker_emulate_start(ibutton->key_worker, key); |     ibutton_worker_emulate_start(ibutton->key_worker, key); | ||||||
|  | 
 | ||||||
|  |     string_clear(key_name); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) { | bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) { | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "../ibutton_i.h" | #include "../ibutton_i.h" | ||||||
|  | #include <toolbox/path.h> | ||||||
| 
 | 
 | ||||||
| void ibutton_scene_info_on_enter(void* context) { | void ibutton_scene_info_on_enter(void* context) { | ||||||
|     iButton* ibutton = context; |     iButton* ibutton = context; | ||||||
| @ -7,7 +8,11 @@ void ibutton_scene_info_on_enter(void* context) { | |||||||
| 
 | 
 | ||||||
|     const uint8_t* key_data = ibutton_key_get_data_p(key); |     const uint8_t* key_data = ibutton_key_get_data_p(key); | ||||||
| 
 | 
 | ||||||
|     ibutton_text_store_set(ibutton, "%s", ibutton_key_get_name_p(key)); |     string_t key_name; | ||||||
|  |     string_init(key_name); | ||||||
|  |     path_extract_filename(ibutton->file_path, key_name, true); | ||||||
|  | 
 | ||||||
|  |     ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name)); | ||||||
|     widget_add_text_box_element( |     widget_add_text_box_element( | ||||||
|         widget, 0, 0, 128, 28, AlignCenter, AlignCenter, ibutton->text_store, false); |         widget, 0, 0, 128, 28, AlignCenter, AlignCenter, ibutton->text_store, false); | ||||||
| 
 | 
 | ||||||
| @ -46,6 +51,8 @@ void ibutton_scene_info_on_enter(void* context) { | |||||||
|         widget, 64, 35, AlignCenter, AlignBottom, FontPrimary, ibutton->text_store); |         widget, 64, 35, AlignCenter, AlignBottom, FontPrimary, ibutton->text_store); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); |     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); | ||||||
|  | 
 | ||||||
|  |     string_clear(key_name); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) { | bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) { | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ void ibutton_scene_read_on_enter(void* context) { | |||||||
|     popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59); |     popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); |     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); | ||||||
|     ibutton_key_set_name(key, ""); |     string_set_str(ibutton->file_path, IBUTTON_APP_FOLDER); | ||||||
| 
 | 
 | ||||||
|     ibutton_worker_read_set_callback(worker, ibutton_scene_read_callback, ibutton); |     ibutton_worker_read_set_callback(worker, ibutton_scene_read_callback, ibutton); | ||||||
|     ibutton_worker_read_start(worker, key); |     ibutton_worker_read_start(worker, key); | ||||||
|  | |||||||
| @ -61,11 +61,7 @@ void ibutton_scene_read_crc_error_on_exit(void* context) { | |||||||
| 
 | 
 | ||||||
|     ibutton_text_store_clear(ibutton); |     ibutton_text_store_clear(ibutton); | ||||||
| 
 | 
 | ||||||
|     dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); |     dialog_ex_reset(dialog_ex); | ||||||
|     dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); |  | ||||||
|     dialog_ex_set_left_button_text(dialog_ex, NULL); |  | ||||||
|     dialog_ex_set_result_callback(dialog_ex, NULL); |  | ||||||
|     dialog_ex_set_context(dialog_ex, NULL); |  | ||||||
| 
 | 
 | ||||||
|     ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff); |     ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff); | ||||||
| } | } | ||||||
|  | |||||||
| @ -62,11 +62,7 @@ void ibutton_scene_read_not_key_error_on_exit(void* context) { | |||||||
| 
 | 
 | ||||||
|     ibutton_text_store_clear(ibutton); |     ibutton_text_store_clear(ibutton); | ||||||
| 
 | 
 | ||||||
|     dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); |     dialog_ex_reset(dialog_ex); | ||||||
|     dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); |  | ||||||
|     dialog_ex_set_left_button_text(dialog_ex, NULL); |  | ||||||
|     dialog_ex_set_result_callback(dialog_ex, NULL); |  | ||||||
|     dialog_ex_set_context(dialog_ex, NULL); |  | ||||||
| 
 | 
 | ||||||
|     ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff); |     ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff); | ||||||
| } | } | ||||||
|  | |||||||
| @ -76,12 +76,7 @@ void ibutton_scene_read_success_on_exit(void* context) { | |||||||
| 
 | 
 | ||||||
|     ibutton_text_store_clear(ibutton); |     ibutton_text_store_clear(ibutton); | ||||||
| 
 | 
 | ||||||
|     dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); |     dialog_ex_reset(dialog_ex); | ||||||
|     dialog_ex_set_left_button_text(dialog_ex, NULL); |  | ||||||
|     dialog_ex_set_right_button_text(dialog_ex, NULL); |  | ||||||
|     dialog_ex_set_result_callback(dialog_ex, NULL); |  | ||||||
|     dialog_ex_set_context(dialog_ex, NULL); |  | ||||||
|     dialog_ex_set_icon(dialog_ex, 0, 0, NULL); |  | ||||||
| 
 | 
 | ||||||
|     ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOff); |     ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOff); | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,5 +1,7 @@ | |||||||
| #include "../ibutton_i.h" | #include "../ibutton_i.h" | ||||||
|  | #include "m-string.h" | ||||||
| #include <lib/toolbox/random_name.h> | #include <lib/toolbox/random_name.h> | ||||||
|  | #include <toolbox/path.h> | ||||||
| 
 | 
 | ||||||
| static void ibutton_scene_save_name_text_input_callback(void* context) { | static void ibutton_scene_save_name_text_input_callback(void* context) { | ||||||
|     iButton* ibutton = context; |     iButton* ibutton = context; | ||||||
| @ -10,13 +12,17 @@ void ibutton_scene_save_name_on_enter(void* context) { | |||||||
|     iButton* ibutton = context; |     iButton* ibutton = context; | ||||||
|     TextInput* text_input = ibutton->text_input; |     TextInput* text_input = ibutton->text_input; | ||||||
| 
 | 
 | ||||||
|     const char* key_name = ibutton_key_get_name_p(ibutton->key); |     string_t key_name; | ||||||
|     const bool key_name_is_empty = !strcmp(key_name, ""); |     string_init(key_name); | ||||||
|  |     if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { | ||||||
|  |         path_extract_filename(ibutton->file_path, key_name, true); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|  |     const bool key_name_is_empty = string_empty_p(key_name); | ||||||
|     if(key_name_is_empty) { |     if(key_name_is_empty) { | ||||||
|         set_random_name(ibutton->text_store, IBUTTON_TEXT_STORE_SIZE); |         set_random_name(ibutton->text_store, IBUTTON_TEXT_STORE_SIZE); | ||||||
|     } else { |     } else { | ||||||
|         ibutton_text_store_set(ibutton, "%s", key_name); |         ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     text_input_set_header_text(text_input, "Name the key"); |     text_input_set_header_text(text_input, "Name the key"); | ||||||
| @ -28,11 +34,19 @@ void ibutton_scene_save_name_on_enter(void* context) { | |||||||
|         IBUTTON_KEY_NAME_SIZE, |         IBUTTON_KEY_NAME_SIZE, | ||||||
|         key_name_is_empty); |         key_name_is_empty); | ||||||
| 
 | 
 | ||||||
|     ValidatorIsFile* validator_is_file = |     string_t folder_path; | ||||||
|         validator_is_file_alloc_init(IBUTTON_APP_FOLDER, IBUTTON_APP_EXTENSION, key_name); |     string_init(folder_path); | ||||||
|  | 
 | ||||||
|  |     path_extract_dirname(string_get_cstr(ibutton->file_path), folder_path); | ||||||
|  | 
 | ||||||
|  |     ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( | ||||||
|  |         string_get_cstr(folder_path), IBUTTON_APP_EXTENSION, string_get_cstr(key_name)); | ||||||
|     text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); |     text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewTextInput); |     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewTextInput); | ||||||
|  | 
 | ||||||
|  |     string_clear(key_name); | ||||||
|  |     string_clear(folder_path); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) { | bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) { | ||||||
|  | |||||||
| @ -36,6 +36,7 @@ bool ibutton_scene_start_on_event(void* context, SceneManagerEvent event) { | |||||||
|         if(event.event == SubmenuIndexRead) { |         if(event.event == SubmenuIndexRead) { | ||||||
|             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRead); |             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRead); | ||||||
|         } else if(event.event == SubmenuIndexSaved) { |         } else if(event.event == SubmenuIndexSaved) { | ||||||
|  |             string_set_str(ibutton->file_path, IBUTTON_APP_FOLDER); | ||||||
|             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSelectKey); |             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSelectKey); | ||||||
|         } else if(event.event == SubmenuIndexAdd) { |         } else if(event.event == SubmenuIndexAdd) { | ||||||
|             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddType); |             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddType); | ||||||
|  | |||||||
| @ -1,4 +1,6 @@ | |||||||
| #include "../ibutton_i.h" | #include "../ibutton_i.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | #include "toolbox/path.h" | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     iButtonSceneWriteStateDefault, |     iButtonSceneWriteStateDefault, | ||||||
| @ -17,13 +19,18 @@ void ibutton_scene_write_on_enter(void* context) { | |||||||
|     iButtonWorker* worker = ibutton->key_worker; |     iButtonWorker* worker = ibutton->key_worker; | ||||||
| 
 | 
 | ||||||
|     const uint8_t* key_data = ibutton_key_get_data_p(key); |     const uint8_t* key_data = ibutton_key_get_data_p(key); | ||||||
|     const char* key_name = ibutton_key_get_name_p(key); | 
 | ||||||
|  |     string_t key_name; | ||||||
|  |     string_init(key_name); | ||||||
|  |     if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { | ||||||
|  |         path_extract_filename(ibutton->file_path, key_name, true); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     uint8_t line_count = 2; |     uint8_t line_count = 2; | ||||||
| 
 | 
 | ||||||
|     // check that stored key has name
 |     // check that stored key has name
 | ||||||
|     if(strcmp(key_name, "") != 0) { |     if(!string_empty_p(key_name)) { | ||||||
|         ibutton_text_store_set(ibutton, "writing\n%s", key_name); |         ibutton_text_store_set(ibutton, "writing\n%s", string_get_cstr(key_name)); | ||||||
|         line_count = 2; |         line_count = 2; | ||||||
|     } else { |     } else { | ||||||
|         // if not, show key data
 |         // if not, show key data
 | ||||||
| @ -79,6 +86,8 @@ void ibutton_scene_write_on_enter(void* context) { | |||||||
| 
 | 
 | ||||||
|     ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton); |     ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton); | ||||||
|     ibutton_worker_write_start(worker, key); |     ibutton_worker_write_start(worker, key); | ||||||
|  | 
 | ||||||
|  |     string_clear(key_name); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) { | bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) { | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| #include <furi_hal_delay.h> | #include <furi_hal_delay.h> | ||||||
| #include <infrared.h> | #include <infrared.h> | ||||||
| #include <app_template.h> |  | ||||||
| #include <cli/cli.h> | #include <cli/cli.h> | ||||||
| #include <cmsis_os2.h> | #include <cmsis_os2.h> | ||||||
| #include <infrared_worker.h> | #include <infrared_worker.h> | ||||||
| @ -86,7 +85,7 @@ static void infrared_cli_print_usage(void) { | |||||||
|     } |     } | ||||||
|     printf("\r\n"); |     printf("\r\n"); | ||||||
|     printf("\tRaw format:\r\n"); |     printf("\tRaw format:\r\n"); | ||||||
|     printf("\tir_tx RAW F:<frequency> DC:<duty_cycle> <sample0> <sample1>...\r\n"); |     printf("\tir tx RAW F:<frequency> DC:<duty_cycle> <sample0> <sample1>...\r\n"); | ||||||
|     printf( |     printf( | ||||||
|         "\tFrequency (%d - %d), Duty cycle (0 - 100), max 512 samples\r\n", |         "\tFrequency (%d - %d), Duty cycle (0 - 100), max 512 samples\r\n", | ||||||
|         INFRARED_MIN_FREQUENCY, |         INFRARED_MIN_FREQUENCY, | ||||||
| @ -178,7 +177,7 @@ static void infrared_cli_start_ir(Cli* cli, string_t args, void* context) { | |||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             if(string_get_cstr(args)[size] == ' ') { |             if(string_get_cstr(args)[size] == ' ') { | ||||||
|                 string_right(args, size); |                 string_right(args, size + 1); | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "infrared_app.h" | #include "infrared_app.h" | ||||||
|  | #include "m-string.h" | ||||||
| #include <infrared_worker.h> | #include <infrared_worker.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| @ -12,20 +13,18 @@ int32_t InfraredApp::run(void* args) { | |||||||
|     bool exit = false; |     bool exit = false; | ||||||
| 
 | 
 | ||||||
|     if(args) { |     if(args) { | ||||||
|         std::string path = static_cast<const char*>(args); |         string_t path; | ||||||
|         std::string remote_name(path, path.find_last_of('/') + 1, path.size()); |         string_init_set_str(path, (char*)args); | ||||||
|         auto last_dot = remote_name.find_last_of('.'); |         if(string_end_with_str_p(path, InfraredApp::infrared_extension)) { | ||||||
|         if(last_dot != std::string::npos) { |             bool result = remote_manager.load(path); | ||||||
|             remote_name.erase(last_dot); |  | ||||||
|             path.erase(path.find_last_of('/')); |  | ||||||
|             bool result = remote_manager.load(path, remote_name); |  | ||||||
|             if(result) { |             if(result) { | ||||||
|                 current_scene = InfraredApp::Scene::Remote; |                 current_scene = InfraredApp::Scene::Remote; | ||||||
|             } else { |             } else { | ||||||
|                 printf("Failed to load remote \'%s\'\r\n", remote_name.c_str()); |                 printf("Failed to load remote \'%s\'\r\n", string_get_cstr(path)); | ||||||
|                 return -1; |                 return -1; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         string_clear(path); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     scenes[current_scene]->on_enter(this); |     scenes[current_scene]->on_enter(this); | ||||||
| @ -51,6 +50,7 @@ int32_t InfraredApp::run(void* args) { | |||||||
| 
 | 
 | ||||||
| InfraredApp::InfraredApp() { | InfraredApp::InfraredApp() { | ||||||
|     furi_check(InfraredAppRemoteManager::max_button_name_length < get_text_store_size()); |     furi_check(InfraredAppRemoteManager::max_button_name_length < get_text_store_size()); | ||||||
|  |     string_init_set_str(file_path, InfraredApp::infrared_directory); | ||||||
|     notification = static_cast<NotificationApp*>(furi_record_open("notification")); |     notification = static_cast<NotificationApp*>(furi_record_open("notification")); | ||||||
|     dialogs = static_cast<DialogsApp*>(furi_record_open("dialogs")); |     dialogs = static_cast<DialogsApp*>(furi_record_open("dialogs")); | ||||||
|     infrared_worker = infrared_worker_alloc(); |     infrared_worker = infrared_worker_alloc(); | ||||||
| @ -60,6 +60,7 @@ InfraredApp::~InfraredApp() { | |||||||
|     infrared_worker_free(infrared_worker); |     infrared_worker_free(infrared_worker); | ||||||
|     furi_record_close("notification"); |     furi_record_close("notification"); | ||||||
|     furi_record_close("dialogs"); |     furi_record_close("dialogs"); | ||||||
|  |     string_clear(file_path); | ||||||
|     for(auto& [key, scene] : scenes) delete scene; |     for(auto& [key, scene] : scenes) delete scene; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -251,6 +251,8 @@ public: | |||||||
|     /** Main class destructor, deinitializes all critical objects */ |     /** Main class destructor, deinitializes all critical objects */ | ||||||
|     ~InfraredApp(); |     ~InfraredApp(); | ||||||
| 
 | 
 | ||||||
|  |     string_t file_path; | ||||||
|  | 
 | ||||||
|     /** Path to Infrared directory */ |     /** Path to Infrared directory */ | ||||||
|     static constexpr const char* infrared_directory = "/any/infrared"; |     static constexpr const char* infrared_directory = "/any/infrared"; | ||||||
|     /** Infrared files extension (remote files and universal databases) */ |     /** Infrared files extension (remote files and universal databases) */ | ||||||
|  | |||||||
| @ -1,3 +1,5 @@ | |||||||
|  | #include "m-string.h" | ||||||
|  | #include "storage/filesystem_api_defines.h" | ||||||
| #include <flipper_format/flipper_format.h> | #include <flipper_format/flipper_format.h> | ||||||
| #include "infrared_app_remote_manager.h" | #include "infrared_app_remote_manager.h" | ||||||
| #include "infrared/helpers/infrared_parser.h" | #include "infrared/helpers/infrared_parser.h" | ||||||
| @ -11,44 +13,58 @@ | |||||||
| #include <gui/modules/button_menu.h> | #include <gui/modules/button_menu.h> | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| #include "infrared_app.h" | #include "infrared_app.h" | ||||||
|  | #include <toolbox/path.h> | ||||||
| 
 | 
 | ||||||
| static const char* default_remote_name = "remote"; | static const char* default_remote_name = "remote"; | ||||||
| 
 | 
 | ||||||
| std::string InfraredAppRemoteManager::make_full_name( | void InfraredAppRemoteManager::find_vacant_remote_name(string_t name, string_t path) { | ||||||
|     const std::string& path, |  | ||||||
|     const std::string& remote_name) const { |  | ||||||
|     return std::string("") + path + "/" + remote_name + InfraredApp::infrared_extension; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::string InfraredAppRemoteManager::find_vacant_remote_name(const std::string& name) { |  | ||||||
|     std::string result_name; |  | ||||||
|     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); |     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); | ||||||
| 
 | 
 | ||||||
|     FS_Error error = storage_common_stat( |     string_t base_path; | ||||||
|         storage, make_full_name(InfraredApp::infrared_directory, name).c_str(), NULL); |     string_init_set(base_path, path); | ||||||
| 
 | 
 | ||||||
|     if(error == FSE_NOT_EXIST) { |     if(string_end_with_str_p(base_path, InfraredApp::infrared_extension)) { | ||||||
|         result_name = name; |         size_t filename_start = string_search_rchar(base_path, '/'); | ||||||
|     } else if(error != FSE_OK) { |         string_left(base_path, filename_start); | ||||||
|         result_name = std::string(); |     } | ||||||
|     } else { | 
 | ||||||
|  |     string_printf( | ||||||
|  |         base_path, | ||||||
|  |         "%s/%s%s", | ||||||
|  |         string_get_cstr(path), | ||||||
|  |         string_get_cstr(name), | ||||||
|  |         InfraredApp::infrared_extension); | ||||||
|  | 
 | ||||||
|  |     FS_Error error = storage_common_stat(storage, string_get_cstr(base_path), NULL); | ||||||
|  | 
 | ||||||
|  |     if(error == FSE_OK) { | ||||||
|         /* if suggested name is occupied, try another one (name2, name3, etc) */ |         /* if suggested name is occupied, try another one (name2, name3, etc) */ | ||||||
|  |         size_t dot = string_search_rchar(base_path, '.'); | ||||||
|  |         string_left(base_path, dot); | ||||||
|  | 
 | ||||||
|  |         string_t path_temp; | ||||||
|  |         string_init(path_temp); | ||||||
|  | 
 | ||||||
|         uint32_t i = 1; |         uint32_t i = 1; | ||||||
|         std::string new_name; |  | ||||||
|         do { |         do { | ||||||
|             new_name = make_full_name(InfraredApp::infrared_directory, name + std::to_string(++i)); |             string_printf( | ||||||
|             error = storage_common_stat(storage, new_name.c_str(), NULL); |                 path_temp, | ||||||
|  |                 "%s%u%s", | ||||||
|  |                 string_get_cstr(base_path), | ||||||
|  |                 ++i, | ||||||
|  |                 InfraredApp::infrared_extension); | ||||||
|  |             error = storage_common_stat(storage, string_get_cstr(path_temp), NULL); | ||||||
|         } while(error == FSE_OK); |         } while(error == FSE_OK); | ||||||
| 
 | 
 | ||||||
|  |         string_clear(path_temp); | ||||||
|  | 
 | ||||||
|         if(error == FSE_NOT_EXIST) { |         if(error == FSE_NOT_EXIST) { | ||||||
|             result_name = name + std::to_string(i); |             string_cat_printf(name, "%u", i); | ||||||
|         } else { |  | ||||||
|             result_name = std::string(); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     string_clear(base_path); | ||||||
|     furi_record_close("storage"); |     furi_record_close("storage"); | ||||||
|     return result_name; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool InfraredAppRemoteManager::add_button(const char* button_name, const InfraredAppSignal& signal) { | bool InfraredAppRemoteManager::add_button(const char* button_name, const InfraredAppSignal& signal) { | ||||||
| @ -61,12 +77,23 @@ bool InfraredAppRemoteManager::add_remote_with_button( | |||||||
|     const InfraredAppSignal& signal) { |     const InfraredAppSignal& signal) { | ||||||
|     furi_check(button_name != nullptr); |     furi_check(button_name != nullptr); | ||||||
| 
 | 
 | ||||||
|     auto new_name = find_vacant_remote_name(default_remote_name); |     string_t new_name; | ||||||
|     if(new_name.empty()) { |     string_init_set_str(new_name, default_remote_name); | ||||||
|         return false; | 
 | ||||||
|     } |     string_t new_path; | ||||||
|  |     string_init_set_str(new_path, InfraredApp::infrared_directory); | ||||||
|  | 
 | ||||||
|  |     find_vacant_remote_name(new_name, new_path); | ||||||
|  | 
 | ||||||
|  |     string_cat_printf( | ||||||
|  |         new_path, "/%s%s", string_get_cstr(new_name), InfraredApp::infrared_extension); | ||||||
|  | 
 | ||||||
|  |     remote = std::make_unique<InfraredAppRemote>(new_path); | ||||||
|  |     remote->name = std::string(string_get_cstr(new_name)); | ||||||
|  | 
 | ||||||
|  |     string_clear(new_path); | ||||||
|  |     string_clear(new_name); | ||||||
| 
 | 
 | ||||||
|     remote = std::make_unique<InfraredAppRemote>(InfraredApp::infrared_directory, new_name); |  | ||||||
|     return add_button(button_name, signal); |     return add_button(button_name, signal); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -93,8 +120,7 @@ const InfraredAppSignal& InfraredAppRemoteManager::get_button_data(size_t index) | |||||||
| bool InfraredAppRemoteManager::delete_remote() { | bool InfraredAppRemoteManager::delete_remote() { | ||||||
|     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); |     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); | ||||||
| 
 | 
 | ||||||
|     FS_Error error = |     FS_Error error = storage_common_remove(storage, string_get_cstr(remote->path)); | ||||||
|         storage_common_remove(storage, make_full_name(remote->path, remote->name).c_str()); |  | ||||||
|     reset_remote(); |     reset_remote(); | ||||||
| 
 | 
 | ||||||
|     furi_record_close("storage"); |     furi_record_close("storage"); | ||||||
| @ -128,22 +154,33 @@ std::string InfraredAppRemoteManager::get_remote_name() { | |||||||
| bool InfraredAppRemoteManager::rename_remote(const char* str) { | bool InfraredAppRemoteManager::rename_remote(const char* str) { | ||||||
|     furi_check(str != nullptr); |     furi_check(str != nullptr); | ||||||
|     furi_check(remote.get() != nullptr); |     furi_check(remote.get() != nullptr); | ||||||
|  |     furi_check(!string_empty_p(remote->path)); | ||||||
| 
 | 
 | ||||||
|     if(!remote->name.compare(str)) { |     if(!remote->name.compare(str)) { | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     auto new_name = find_vacant_remote_name(str); |     string_t new_name; | ||||||
|     if(new_name.empty()) { |     string_init_set_str(new_name, str); | ||||||
|         return false; |     find_vacant_remote_name(new_name, remote->path); | ||||||
|  | 
 | ||||||
|  |     string_t new_path; | ||||||
|  |     string_init_set(new_path, remote->path); | ||||||
|  |     if(string_end_with_str_p(new_path, InfraredApp::infrared_extension)) { | ||||||
|  |         size_t filename_start = string_search_rchar(new_path, '/'); | ||||||
|  |         string_left(new_path, filename_start); | ||||||
|     } |     } | ||||||
|  |     string_cat_printf( | ||||||
|  |         new_path, "/%s%s", string_get_cstr(new_name), InfraredApp::infrared_extension); | ||||||
| 
 | 
 | ||||||
|     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); |     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); | ||||||
| 
 | 
 | ||||||
|     std::string old_filename = make_full_name(remote->path, remote->name); |     FS_Error error = | ||||||
|     std::string new_filename = make_full_name(remote->path, new_name); |         storage_common_rename(storage, string_get_cstr(remote->path), string_get_cstr(new_path)); | ||||||
|     FS_Error error = storage_common_rename(storage, old_filename.c_str(), new_filename.c_str()); |     remote->name = std::string(string_get_cstr(new_name)); | ||||||
|     remote->name = new_name; | 
 | ||||||
|  |     string_clear(new_name); | ||||||
|  |     string_clear(new_path); | ||||||
| 
 | 
 | ||||||
|     furi_record_close("storage"); |     furi_record_close("storage"); | ||||||
|     return (error == FSE_OK || error == FSE_EXIST); |     return (error == FSE_OK || error == FSE_EXIST); | ||||||
| @ -171,10 +208,8 @@ bool InfraredAppRemoteManager::store(void) { | |||||||
| 
 | 
 | ||||||
|     FlipperFormat* ff = flipper_format_file_alloc(storage); |     FlipperFormat* ff = flipper_format_file_alloc(storage); | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I( |     FURI_LOG_I("RemoteManager", "store file: \'%s\'", string_get_cstr(remote->path)); | ||||||
|         "RemoteManager", "store file: \'%s\'", make_full_name(remote->path, remote->name).c_str()); |     result = flipper_format_file_open_always(ff, string_get_cstr(remote->path)); | ||||||
|     result = |  | ||||||
|         flipper_format_file_open_always(ff, make_full_name(remote->path, remote->name).c_str()); |  | ||||||
|     if(result) { |     if(result) { | ||||||
|         result = flipper_format_write_header_cstr(ff, "IR signals file", 1); |         result = flipper_format_write_header_cstr(ff, "IR signals file", 1); | ||||||
|     } |     } | ||||||
| @ -192,13 +227,13 @@ bool InfraredAppRemoteManager::store(void) { | |||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool InfraredAppRemoteManager::load(const std::string& path, const std::string& remote_name) { | bool InfraredAppRemoteManager::load(string_t path) { | ||||||
|     bool result = false; |     bool result = false; | ||||||
|     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); |     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); | ||||||
|     FlipperFormat* ff = flipper_format_file_alloc(storage); |     FlipperFormat* ff = flipper_format_file_alloc(storage); | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I("RemoteManager", "load file: \'%s\'", make_full_name(path, remote_name).c_str()); |     FURI_LOG_I("RemoteManager", "load file: \'%s\'", string_get_cstr(path)); | ||||||
|     result = flipper_format_file_open_existing(ff, make_full_name(path, remote_name).c_str()); |     result = flipper_format_file_open_existing(ff, string_get_cstr(path)); | ||||||
|     if(result) { |     if(result) { | ||||||
|         string_t header; |         string_t header; | ||||||
|         string_init(header); |         string_init(header); | ||||||
| @ -210,7 +245,14 @@ bool InfraredAppRemoteManager::load(const std::string& path, const std::string& | |||||||
|         string_clear(header); |         string_clear(header); | ||||||
|     } |     } | ||||||
|     if(result) { |     if(result) { | ||||||
|         remote = std::make_unique<InfraredAppRemote>(path, remote_name); |         string_t new_name; | ||||||
|  |         string_init(new_name); | ||||||
|  | 
 | ||||||
|  |         remote = std::make_unique<InfraredAppRemote>(path); | ||||||
|  |         path_extract_filename(path, new_name, true); | ||||||
|  |         remote->name = std::string(string_get_cstr(new_name)); | ||||||
|  | 
 | ||||||
|  |         string_clear(new_name); | ||||||
|         InfraredAppSignal signal; |         InfraredAppSignal signal; | ||||||
|         std::string signal_name; |         std::string signal_name; | ||||||
|         while(infrared_parser_read_signal(ff, signal, signal_name)) { |         while(infrared_parser_read_signal(ff, signal, signal_name)) { | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ | |||||||
| 
 | 
 | ||||||
| #include "infrared_app_signal.h" | #include "infrared_app_signal.h" | ||||||
| 
 | 
 | ||||||
|  | #include "m-string.h" | ||||||
| #include <infrared_worker.h> | #include <infrared_worker.h> | ||||||
| #include <infrared.h> | #include <infrared.h> | ||||||
| 
 | 
 | ||||||
| @ -60,17 +61,19 @@ class InfraredAppRemote { | |||||||
|     /** Name of remote */ |     /** Name of remote */ | ||||||
|     std::string name; |     std::string name; | ||||||
|     /** Path to remote file */ |     /** Path to remote file */ | ||||||
|     std::string path; |     string_t path; | ||||||
| 
 | 
 | ||||||
| public: | public: | ||||||
|     /** Initialize new remote
 |     /** Initialize new remote
 | ||||||
|      *  |      *  | ||||||
|      * @param path - remote file path |      * @param path - remote file path | ||||||
|      * @param name - new remote name |  | ||||||
|      */ |      */ | ||||||
|     InfraredAppRemote(const std::string& path, const std::string& name) |     InfraredAppRemote(string_t file_path) { | ||||||
|         : name(name) |         string_init_set(path, file_path); | ||||||
|         , path(path) { |     } | ||||||
|  | 
 | ||||||
|  |     ~InfraredAppRemote() { | ||||||
|  |         string_clear(path); | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -78,12 +81,6 @@ public: | |||||||
| class InfraredAppRemoteManager { | class InfraredAppRemoteManager { | ||||||
|     /** Remote instance. There can be 1 remote loaded at a time. */ |     /** Remote instance. There can be 1 remote loaded at a time. */ | ||||||
|     std::unique_ptr<InfraredAppRemote> remote; |     std::unique_ptr<InfraredAppRemote> remote; | ||||||
|     /** Make full name from remote name
 |  | ||||||
|      * |  | ||||||
|      * @param remote_name name of remote |  | ||||||
|      * @retval full name of remote on disk |  | ||||||
|      */ |  | ||||||
|     std::string make_full_name(const std::string& path, const std::string& remote_name) const; |  | ||||||
| 
 | 
 | ||||||
| public: | public: | ||||||
|     /** Restriction to button name length. Buttons larger are ignored. */ |     /** Restriction to button name length. Buttons larger are ignored. */ | ||||||
| @ -125,9 +122,9 @@ public: | |||||||
|      * incremented digit(2,3,4,etc) added to name and check repeated. |      * incremented digit(2,3,4,etc) added to name and check repeated. | ||||||
|      * |      * | ||||||
|      * @param name - suggested remote name |      * @param name - suggested remote name | ||||||
|      * @retval garanteed free remote name, prefixed with suggested |      * @param path - remote file path | ||||||
|      */ |      */ | ||||||
|     std::string find_vacant_remote_name(const std::string& name); |     void find_vacant_remote_name(string_t name, string_t path); | ||||||
| 
 | 
 | ||||||
|     /** Get button list
 |     /** Get button list
 | ||||||
|      * |      * | ||||||
| @ -185,8 +182,8 @@ public: | |||||||
| 
 | 
 | ||||||
|     /** Load data from disk into current remote
 |     /** Load data from disk into current remote
 | ||||||
|      * |      * | ||||||
|      * @param name - name of remote to load |      * @param path - path to remote file | ||||||
|      * @retval true if success, false otherwise |      * @retval true if success, false otherwise | ||||||
|      */ |      */ | ||||||
|     bool load(const std::string& path, const std::string& name); |     bool load(string_t path); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,4 +1,6 @@ | |||||||
| #include "../infrared_app.h" | #include "../infrared_app.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | #include "toolbox/path.h" | ||||||
| 
 | 
 | ||||||
| void InfraredAppSceneEditRename::on_enter(InfraredApp* app) { | void InfraredAppSceneEditRename::on_enter(InfraredApp* app) { | ||||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); |     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||||
| @ -21,9 +23,18 @@ void InfraredAppSceneEditRename::on_enter(InfraredApp* app) { | |||||||
|         enter_name_length = InfraredAppRemoteManager::max_remote_name_length; |         enter_name_length = InfraredAppRemoteManager::max_remote_name_length; | ||||||
|         text_input_set_header_text(text_input, "Name the remote"); |         text_input_set_header_text(text_input, "Name the remote"); | ||||||
| 
 | 
 | ||||||
|  |         string_t folder_path; | ||||||
|  |         string_init(folder_path); | ||||||
|  | 
 | ||||||
|  |         if(string_end_with_str_p(app->file_path, InfraredApp::infrared_extension)) { | ||||||
|  |             path_extract_dirname(string_get_cstr(app->file_path), folder_path); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( |         ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( | ||||||
|             app->infrared_directory, app->infrared_extension, remote_name.c_str()); |             string_get_cstr(folder_path), app->infrared_extension, remote_name.c_str()); | ||||||
|         text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); |         text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); | ||||||
|  | 
 | ||||||
|  |         string_clear(folder_path); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     text_input_set_result_callback( |     text_input_set_result_callback( | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "../infrared_app.h" | #include "../infrared_app.h" | ||||||
|  | #include "assets_icons.h" | ||||||
| #include "infrared/infrared_app_event.h" | #include "infrared/infrared_app_event.h" | ||||||
| #include <text_store.h> | #include <text_store.h> | ||||||
| 
 | 
 | ||||||
| @ -8,11 +9,6 @@ void InfraredAppSceneRemoteList::on_enter(InfraredApp* app) { | |||||||
|     bool result = false; |     bool result = false; | ||||||
|     bool file_select_result; |     bool file_select_result; | ||||||
|     auto remote_manager = app->get_remote_manager(); |     auto remote_manager = app->get_remote_manager(); | ||||||
|     auto last_selected_remote = remote_manager->get_remote_name(); |  | ||||||
|     const char* last_selected_remote_name = |  | ||||||
|         last_selected_remote.size() ? last_selected_remote.c_str() : nullptr; |  | ||||||
|     auto filename_ts = |  | ||||||
|         std::make_unique<TextStore>(InfraredAppRemoteManager::max_remote_name_length); |  | ||||||
|     DialogsApp* dialogs = app->get_dialogs(); |     DialogsApp* dialogs = app->get_dialogs(); | ||||||
| 
 | 
 | ||||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); |     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||||
| @ -20,16 +16,17 @@ void InfraredAppSceneRemoteList::on_enter(InfraredApp* app) { | |||||||
|     button_menu_reset(button_menu); |     button_menu_reset(button_menu); | ||||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::ButtonMenu); |     view_manager->switch_to(InfraredAppViewManager::ViewId::ButtonMenu); | ||||||
| 
 | 
 | ||||||
|     file_select_result = dialog_file_select_show( |     file_select_result = dialog_file_browser_show( | ||||||
|         dialogs, |         dialogs, | ||||||
|         InfraredApp::infrared_directory, |         app->file_path, | ||||||
|  |         app->file_path, | ||||||
|         InfraredApp::infrared_extension, |         InfraredApp::infrared_extension, | ||||||
|         filename_ts->text, |         true, | ||||||
|         filename_ts->text_size, |         &I_ir_10px, | ||||||
|         last_selected_remote_name); |         true); | ||||||
| 
 | 
 | ||||||
|     if(file_select_result) { |     if(file_select_result) { | ||||||
|         if(remote_manager->load(InfraredApp::infrared_directory, std::string(filename_ts->text))) { |         if(remote_manager->load(app->file_path)) { | ||||||
|             app->switch_to_next_scene(InfraredApp::Scene::Remote); |             app->switch_to_next_scene(InfraredApp::Scene::Remote); | ||||||
|             result = true; |             result = true; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -26,6 +26,8 @@ void InfraredAppSceneStart::on_enter(InfraredApp* app) { | |||||||
|         submenu, "Learn New Remote", SubmenuIndexLearnNewRemote, submenu_callback, app); |         submenu, "Learn New Remote", SubmenuIndexLearnNewRemote, submenu_callback, app); | ||||||
|     submenu_add_item(submenu, "Saved Remotes", SubmenuIndexSavedRemotes, submenu_callback, app); |     submenu_add_item(submenu, "Saved Remotes", SubmenuIndexSavedRemotes, submenu_callback, app); | ||||||
|     submenu_set_selected_item(submenu, submenu_item_selected); |     submenu_set_selected_item(submenu, submenu_item_selected); | ||||||
|  | 
 | ||||||
|  |     string_set_str(app->file_path, InfraredApp::infrared_directory); | ||||||
|     submenu_item_selected = 0; |     submenu_item_selected = 0; | ||||||
| 
 | 
 | ||||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu); |     view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu); | ||||||
|  | |||||||
| @ -59,7 +59,7 @@ static void infrared_progress_view_draw_callback(Canvas* canvas, void* _model) { | |||||||
|     elements_multiline_text_aligned( |     elements_multiline_text_aligned( | ||||||
|         canvas, x + 33, y + 37, AlignCenter, AlignCenter, percents_string); |         canvas, x + 33, y + 37, AlignCenter, AlignCenter, percents_string); | ||||||
| 
 | 
 | ||||||
|     canvas_draw_icon(canvas, x + 11, y + height - 15, &I_Back_15x10); |     canvas_draw_icon(canvas, x + 14, y + height - 14, &I_Pin_back_arrow_10x8); | ||||||
|     canvas_draw_str(canvas, x + 30, y + height - 6, "= stop"); |     canvas_draw_str(canvas, x + 30, y + height - 6, "= stop"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										107
									
								
								applications/lfrfid/helpers/decoder_ioprox.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								applications/lfrfid/helpers/decoder_ioprox.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,107 @@ | |||||||
|  | #include "decoder_ioprox.h" | ||||||
|  | #include <furi_hal.h> | ||||||
|  | #include <cli/cli.h> | ||||||
|  | #include <utility> | ||||||
|  | 
 | ||||||
|  | constexpr uint32_t clocks_in_us = 64; | ||||||
|  | 
 | ||||||
|  | constexpr uint32_t jitter_time_us = 20; | ||||||
|  | constexpr uint32_t min_time_us = 64; | ||||||
|  | constexpr uint32_t max_time_us = 80; | ||||||
|  | constexpr uint32_t baud_time_us = 500; | ||||||
|  | 
 | ||||||
|  | constexpr uint32_t min_time = (min_time_us - jitter_time_us) * clocks_in_us; | ||||||
|  | constexpr uint32_t mid_time = ((max_time_us - min_time_us) / 2 + min_time_us) * clocks_in_us; | ||||||
|  | constexpr uint32_t max_time = (max_time_us + jitter_time_us) * clocks_in_us; | ||||||
|  | constexpr uint32_t baud_time = baud_time_us * clocks_in_us; | ||||||
|  | 
 | ||||||
|  | bool DecoderIoProx::read(uint8_t* data, uint8_t data_size) { | ||||||
|  |     bool result = false; | ||||||
|  |     furi_assert(data_size >= 4); | ||||||
|  | 
 | ||||||
|  |     if(ready) { | ||||||
|  |         result = true; | ||||||
|  |         ioprox.decode(raw_data, sizeof(raw_data), data, data_size); | ||||||
|  |         ready = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void DecoderIoProx::process_front(bool is_rising_edge, uint32_t time) { | ||||||
|  |     if(ready) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Always track the time that's gone by.
 | ||||||
|  |     current_period_duration += time; | ||||||
|  |     demodulation_sample_duration += time; | ||||||
|  | 
 | ||||||
|  |     // If a baud time has elapsed, we're at a sample point.
 | ||||||
|  |     if(demodulation_sample_duration >= baud_time) { | ||||||
|  |         // Start a new baud period...
 | ||||||
|  |         demodulation_sample_duration = 0; | ||||||
|  |         demodulated_value_invalid = false; | ||||||
|  | 
 | ||||||
|  |         // ... and if we didn't have any baud errors, capture a sample.
 | ||||||
|  |         if(!demodulated_value_invalid) { | ||||||
|  |             store_data(current_demodulated_value); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     //
 | ||||||
|  |     // FSK demodulator.
 | ||||||
|  |     //
 | ||||||
|  | 
 | ||||||
|  |     // If this isn't a rising edge, this isn't a pulse of interest.
 | ||||||
|  |     // We're done.
 | ||||||
|  |     if(!is_rising_edge) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     bool is_valid_low = (current_period_duration > min_time) && | ||||||
|  |                         (current_period_duration <= mid_time); | ||||||
|  |     bool is_valid_high = (current_period_duration > mid_time) && | ||||||
|  |                          (current_period_duration < max_time); | ||||||
|  | 
 | ||||||
|  |     // If this is between the minimum and our threshold, this is a logical 0.
 | ||||||
|  |     if(is_valid_low) { | ||||||
|  |         current_demodulated_value = false; | ||||||
|  |     } | ||||||
|  |     // Otherwise, if between our threshold and the max time, it's a logical 1.
 | ||||||
|  |     else if(is_valid_high) { | ||||||
|  |         current_demodulated_value = true; | ||||||
|  |     } | ||||||
|  |     // Otherwise, invalidate this sample.
 | ||||||
|  |     else { | ||||||
|  |         demodulated_value_invalid = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // We're starting a new period; track that.
 | ||||||
|  |     current_period_duration = 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | DecoderIoProx::DecoderIoProx() { | ||||||
|  |     reset_state(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void DecoderIoProx::store_data(bool data) { | ||||||
|  |     for(int i = 0; i < 7; ++i) { | ||||||
|  |         raw_data[i] = (raw_data[i] << 1) | ((raw_data[i + 1] >> 7) & 1); | ||||||
|  |     } | ||||||
|  |     raw_data[7] = (raw_data[7] << 1) | data; | ||||||
|  | 
 | ||||||
|  |     if(ioprox.can_be_decoded(raw_data, sizeof(raw_data))) { | ||||||
|  |         ready = true; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void DecoderIoProx::reset_state() { | ||||||
|  |     current_demodulated_value = false; | ||||||
|  |     demodulated_value_invalid = false; | ||||||
|  | 
 | ||||||
|  |     current_period_duration = 0; | ||||||
|  |     demodulation_sample_duration = 0; | ||||||
|  | 
 | ||||||
|  |     ready = false; | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								applications/lfrfid/helpers/decoder_ioprox.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								applications/lfrfid/helpers/decoder_ioprox.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <stdint.h> | ||||||
|  | #include <atomic> | ||||||
|  | #include "protocols/protocol_ioprox.h" | ||||||
|  | 
 | ||||||
|  | class DecoderIoProx { | ||||||
|  | public: | ||||||
|  |     bool read(uint8_t* data, uint8_t data_size); | ||||||
|  |     void process_front(bool polarity, uint32_t time); | ||||||
|  |     DecoderIoProx(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     uint32_t current_period_duration = 0; | ||||||
|  |     uint32_t demodulation_sample_duration = 0; | ||||||
|  | 
 | ||||||
|  |     bool current_demodulated_value = false; | ||||||
|  |     bool demodulated_value_invalid = false; | ||||||
|  | 
 | ||||||
|  |     uint8_t raw_data[8] = {0}; | ||||||
|  |     void store_data(bool data); | ||||||
|  | 
 | ||||||
|  |     std::atomic<bool> ready; | ||||||
|  | 
 | ||||||
|  |     void reset_state(); | ||||||
|  |     ProtocolIoProx ioprox; | ||||||
|  | }; | ||||||
							
								
								
									
										32
									
								
								applications/lfrfid/helpers/encoder_ioprox.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								applications/lfrfid/helpers/encoder_ioprox.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | |||||||
|  | #include "encoder_ioprox.h" | ||||||
|  | #include "protocols/protocol_ioprox.h" | ||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
|  | void EncoderIoProx::init(const uint8_t* data, const uint8_t data_size) { | ||||||
|  |     ProtocolIoProx ioprox; | ||||||
|  |     ioprox.encode(data, data_size, card_data, sizeof(card_data)); | ||||||
|  |     card_data_index = 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void EncoderIoProx::get_next(bool* polarity, uint16_t* period, uint16_t* pulse) { | ||||||
|  |     uint8_t bit = (card_data[card_data_index / 8] >> (7 - (card_data_index % 8))) & 1; | ||||||
|  | 
 | ||||||
|  |     bool advance = fsk->next(bit, period); | ||||||
|  |     if(advance) { | ||||||
|  |         card_data_index++; | ||||||
|  |         if(card_data_index >= (8 * card_data_max)) { | ||||||
|  |             card_data_index = 0; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     *polarity = true; | ||||||
|  |     *pulse = *period / 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | EncoderIoProx::EncoderIoProx() { | ||||||
|  |     fsk = new OscFSK(8, 10, 64); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | EncoderIoProx::~EncoderIoProx() { | ||||||
|  |     delete fsk; | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								applications/lfrfid/helpers/encoder_ioprox.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								applications/lfrfid/helpers/encoder_ioprox.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | |||||||
|  | #pragma once | ||||||
|  | #include "encoder_generic.h" | ||||||
|  | #include "osc_fsk.h" | ||||||
|  | 
 | ||||||
|  | class EncoderIoProx : public EncoderGeneric { | ||||||
|  | public: | ||||||
|  |     /**
 | ||||||
|  |      * @brief init data to emulate | ||||||
|  |      *  | ||||||
|  |      * @param data 1 byte FC, 1 byte Version, 2 bytes code | ||||||
|  |      * @param data_size must be 4 | ||||||
|  |      */ | ||||||
|  |     void init(const uint8_t* data, const uint8_t data_size) final; | ||||||
|  |     void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) final; | ||||||
|  |     EncoderIoProx(); | ||||||
|  |     ~EncoderIoProx(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     static const uint8_t card_data_max = 8; | ||||||
|  | 
 | ||||||
|  |     uint8_t card_data[card_data_max]; | ||||||
|  |     uint8_t card_data_index; | ||||||
|  | 
 | ||||||
|  |     OscFSK* fsk; | ||||||
|  | }; | ||||||
| @ -12,6 +12,9 @@ const char* lfrfid_key_get_type_string(LfrfidKeyType type) { | |||||||
|     case LfrfidKeyType::KeyI40134: |     case LfrfidKeyType::KeyI40134: | ||||||
|         return "I40134"; |         return "I40134"; | ||||||
|         break; |         break; | ||||||
|  |     case LfrfidKeyType::KeyIoProxXSF: | ||||||
|  |         return "IoProxXSF"; | ||||||
|  |         break; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return "Unknown"; |     return "Unknown"; | ||||||
| @ -28,6 +31,8 @@ const char* lfrfid_key_get_manufacturer_string(LfrfidKeyType type) { | |||||||
|     case LfrfidKeyType::KeyI40134: |     case LfrfidKeyType::KeyI40134: | ||||||
|         return "Indala"; |         return "Indala"; | ||||||
|         break; |         break; | ||||||
|  |     case LfrfidKeyType::KeyIoProxXSF: | ||||||
|  |         return "Kantech"; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return "Unknown"; |     return "Unknown"; | ||||||
| @ -42,6 +47,8 @@ bool lfrfid_key_get_string_type(const char* string, LfrfidKeyType* type) { | |||||||
|         *type = LfrfidKeyType::KeyH10301; |         *type = LfrfidKeyType::KeyH10301; | ||||||
|     } else if(strcmp("I40134", string) == 0) { |     } else if(strcmp("I40134", string) == 0) { | ||||||
|         *type = LfrfidKeyType::KeyI40134; |         *type = LfrfidKeyType::KeyI40134; | ||||||
|  |     } else if(strcmp("IoProxXSF", string) == 0) { | ||||||
|  |         *type = LfrfidKeyType::KeyIoProxXSF; | ||||||
|     } else { |     } else { | ||||||
|         result = false; |         result = false; | ||||||
|     } |     } | ||||||
| @ -60,6 +67,9 @@ uint8_t lfrfid_key_get_type_data_count(LfrfidKeyType type) { | |||||||
|     case LfrfidKeyType::KeyI40134: |     case LfrfidKeyType::KeyI40134: | ||||||
|         return 3; |         return 3; | ||||||
|         break; |         break; | ||||||
|  |     case LfrfidKeyType::KeyIoProxXSF: | ||||||
|  |         return 4; | ||||||
|  |         break; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return 0; |     return 0; | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ enum class LfrfidKeyType : uint8_t { | |||||||
|     KeyEM4100, |     KeyEM4100, | ||||||
|     KeyH10301, |     KeyH10301, | ||||||
|     KeyI40134, |     KeyI40134, | ||||||
|  |     KeyIoProxXSF, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const char* lfrfid_key_get_type_string(LfrfidKeyType type); | const char* lfrfid_key_get_type_string(LfrfidKeyType type); | ||||||
|  | |||||||
							
								
								
									
										193
									
								
								applications/lfrfid/helpers/protocols/protocol_ioprox.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								applications/lfrfid/helpers/protocols/protocol_ioprox.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,193 @@ | |||||||
|  | #include "protocol_ioprox.h" | ||||||
|  | #include <furi.h> | ||||||
|  | #include <cli/cli.h> | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Writes a bit into the output buffer. | ||||||
|  |  */ | ||||||
|  | static void write_bit(bool bit, uint8_t position, uint8_t* data) { | ||||||
|  |     if(bit) { | ||||||
|  |         data[position / 8] |= 1UL << (7 - (position % 8)); | ||||||
|  |     } else { | ||||||
|  |         data[position / 8] &= ~(1UL << (7 - (position % 8))); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Writes up to eight contiguous bits into the output buffer. | ||||||
|  |  */ | ||||||
|  | static void write_bits(uint8_t byte, uint8_t position, uint8_t* data, uint8_t length) { | ||||||
|  |     furi_check(length <= 8); | ||||||
|  |     furi_check(length > 0); | ||||||
|  | 
 | ||||||
|  |     for(uint8_t i = 0; i < length; ++i) { | ||||||
|  |         uint8_t shift = 7 - i; | ||||||
|  |         write_bit((byte >> shift) & 1, position + i, data); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | uint8_t ProtocolIoProx::get_encoded_data_size() { | ||||||
|  |     return 8; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | uint8_t ProtocolIoProx::get_decoded_data_size() { | ||||||
|  |     return 4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ProtocolIoProx::encode( | ||||||
|  |     const uint8_t* decoded_data, | ||||||
|  |     const uint8_t decoded_data_size, | ||||||
|  |     uint8_t* encoded_data, | ||||||
|  |     const uint8_t encoded_data_size) { | ||||||
|  |     furi_check(decoded_data_size >= get_decoded_data_size()); | ||||||
|  |     furi_check(encoded_data_size >= get_encoded_data_size()); | ||||||
|  | 
 | ||||||
|  |     // Packet to transmit:
 | ||||||
|  |     //
 | ||||||
|  |     // 0           10          20          30          40          50          60
 | ||||||
|  |     // v           v           v           v           v           v           v
 | ||||||
|  |     // 01234567 8 90123456 7 89012345 6 78901234 5 67890123 4 56789012 3 45678901 23
 | ||||||
|  |     // -----------------------------------------------------------------------------
 | ||||||
|  |     // 00000000 0 11110000 1 facility 1 version_ 1 code-one 1 code-two 1 checksum 11
 | ||||||
|  | 
 | ||||||
|  |     // Preamble.
 | ||||||
|  |     write_bits(0b00000000, 0, encoded_data, 8); | ||||||
|  |     write_bit(0, 8, encoded_data); | ||||||
|  | 
 | ||||||
|  |     write_bits(0b11110000, 9, encoded_data, 8); | ||||||
|  |     write_bit(1, 17, encoded_data); | ||||||
|  | 
 | ||||||
|  |     // Facility code.
 | ||||||
|  |     write_bits(decoded_data[0], 18, encoded_data, 8); | ||||||
|  |     write_bit(1, 26, encoded_data); | ||||||
|  | 
 | ||||||
|  |     // Version
 | ||||||
|  |     write_bits(decoded_data[1], 27, encoded_data, 8); | ||||||
|  |     write_bit(1, 35, encoded_data); | ||||||
|  | 
 | ||||||
|  |     // Code one
 | ||||||
|  |     write_bits(decoded_data[2], 36, encoded_data, 8); | ||||||
|  |     write_bit(1, 44, encoded_data); | ||||||
|  | 
 | ||||||
|  |     // Code two
 | ||||||
|  |     write_bits(decoded_data[3], 45, encoded_data, 8); | ||||||
|  |     write_bit(1, 53, encoded_data); | ||||||
|  | 
 | ||||||
|  |     // Checksum
 | ||||||
|  |     write_bits(compute_checksum(encoded_data, 8), 54, encoded_data, 8); | ||||||
|  |     write_bit(1, 62, encoded_data); | ||||||
|  |     write_bit(1, 63, encoded_data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ProtocolIoProx::decode( | ||||||
|  |     const uint8_t* encoded_data, | ||||||
|  |     const uint8_t encoded_data_size, | ||||||
|  |     uint8_t* decoded_data, | ||||||
|  |     const uint8_t decoded_data_size) { | ||||||
|  |     furi_check(decoded_data_size >= get_decoded_data_size()); | ||||||
|  |     furi_check(encoded_data_size >= get_encoded_data_size()); | ||||||
|  | 
 | ||||||
|  |     // Packet structure:
 | ||||||
|  |     // (Note: the second word seems fixed; but this may not be a guarantee;
 | ||||||
|  |     //  it currently has no meaning.)
 | ||||||
|  |     //
 | ||||||
|  |     //0        1        2        3        4        5        6        7
 | ||||||
|  |     //v        v        v        v        v        v        v        v
 | ||||||
|  |     //01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF
 | ||||||
|  |     //-----------------------------------------------------------------------
 | ||||||
|  |     //00000000 01111000 01FFFFFF FF1VVVVV VVV1CCCC CCCC1CCC CCCCC1XX XXXXXX11
 | ||||||
|  |     //
 | ||||||
|  |     // F = facility code
 | ||||||
|  |     // V = version
 | ||||||
|  |     // C = code
 | ||||||
|  |     // X = checksum
 | ||||||
|  | 
 | ||||||
|  |     // Facility code
 | ||||||
|  |     decoded_data[0] = (encoded_data[2] << 2) | (encoded_data[3] >> 6); | ||||||
|  | 
 | ||||||
|  |     // Version code.
 | ||||||
|  |     decoded_data[1] = (encoded_data[3] << 3) | (encoded_data[4] >> 5); | ||||||
|  | 
 | ||||||
|  |     // Code bytes.
 | ||||||
|  |     decoded_data[2] = (encoded_data[4] << 4) | (encoded_data[5] >> 4); | ||||||
|  |     decoded_data[3] = (encoded_data[5] << 5) | (encoded_data[6] >> 3); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ProtocolIoProx::can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) { | ||||||
|  |     furi_check(encoded_data_size >= get_encoded_data_size()); | ||||||
|  | 
 | ||||||
|  |     // Packet framing
 | ||||||
|  |     //
 | ||||||
|  |     //0        1        2        3        4        5        6        7
 | ||||||
|  |     //v        v        v        v        v        v        v        v
 | ||||||
|  |     //01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF
 | ||||||
|  |     //-----------------------------------------------------------------------
 | ||||||
|  |     //00000000 01______ _1______ __1_____ ___1____ ____1___ _____1XX XXXXXX11
 | ||||||
|  |     //
 | ||||||
|  |     // _ = variable data
 | ||||||
|  |     // 0 = preamble 0
 | ||||||
|  |     // 1 = framing 1
 | ||||||
|  |     // X = checksum
 | ||||||
|  | 
 | ||||||
|  |     // Validate the packet preamble is there...
 | ||||||
|  |     if(encoded_data[0] != 0b00000000) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     if((encoded_data[1] >> 6) != 0b01) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // ... check for known ones...
 | ||||||
|  |     if((encoded_data[2] & 0b01000000) == 0) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     if((encoded_data[3] & 0b00100000) == 0) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     if((encoded_data[4] & 0b00010000) == 0) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     if((encoded_data[5] & 0b00001000) == 0) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     if((encoded_data[6] & 0b00000100) == 0) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     if((encoded_data[7] & 0b00000011) == 0) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // ... and validate our checksums.
 | ||||||
|  |     uint8_t checksum = compute_checksum(encoded_data, 8); | ||||||
|  |     uint8_t checkval = (encoded_data[6] << 6) | (encoded_data[7] >> 2); | ||||||
|  | 
 | ||||||
|  |     if(checksum != checkval) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | uint8_t ProtocolIoProx::compute_checksum(const uint8_t* data, const uint8_t data_size) { | ||||||
|  |     furi_check(data_size == get_encoded_data_size()); | ||||||
|  | 
 | ||||||
|  |     // Packet structure:
 | ||||||
|  |     //
 | ||||||
|  |     //0        1        2         3         4         5         6         7
 | ||||||
|  |     //v        v        v         v         v         v         v         v
 | ||||||
|  |     //01234567 8 9ABCDEF0 1 23456789 A BCDEF012 3 456789AB C DEF01234 5 6789ABCD EF
 | ||||||
|  |     //00000000 0 VVVVVVVV 1 WWWWWWWW 1 XXXXXXXX 1 YYYYYYYY 1 ZZZZZZZZ 1 CHECKSUM 11
 | ||||||
|  |     //
 | ||||||
|  |     // algorithm as observed by the proxmark3 folks
 | ||||||
|  |     // CHECKSUM == 0xFF - (V + W + X + Y + Z)
 | ||||||
|  | 
 | ||||||
|  |     uint8_t checksum = 0; | ||||||
|  | 
 | ||||||
|  |     checksum += (data[1] << 1) | (data[2] >> 7); // VVVVVVVVV
 | ||||||
|  |     checksum += (data[2] << 2) | (data[3] >> 6); // WWWWWWWWW
 | ||||||
|  |     checksum += (data[3] << 3) | (data[4] >> 5); // XXXXXXXXX
 | ||||||
|  |     checksum += (data[4] << 4) | (data[5] >> 4); // YYYYYYYYY
 | ||||||
|  |     checksum += (data[5] << 5) | (data[6] >> 3); // ZZZZZZZZZ
 | ||||||
|  | 
 | ||||||
|  |     return 0xFF - checksum; | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								applications/lfrfid/helpers/protocols/protocol_ioprox.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								applications/lfrfid/helpers/protocols/protocol_ioprox.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | |||||||
|  | #pragma once | ||||||
|  | #include "protocol_generic.h" | ||||||
|  | 
 | ||||||
|  | class ProtocolIoProx : public ProtocolGeneric { | ||||||
|  | public: | ||||||
|  |     uint8_t get_encoded_data_size() final; | ||||||
|  |     uint8_t get_decoded_data_size() final; | ||||||
|  | 
 | ||||||
|  |     void encode( | ||||||
|  |         const uint8_t* decoded_data, | ||||||
|  |         const uint8_t decoded_data_size, | ||||||
|  |         uint8_t* encoded_data, | ||||||
|  |         const uint8_t encoded_data_size) final; | ||||||
|  | 
 | ||||||
|  |     void decode( | ||||||
|  |         const uint8_t* encoded_data, | ||||||
|  |         const uint8_t encoded_data_size, | ||||||
|  |         uint8_t* decoded_data, | ||||||
|  |         const uint8_t decoded_data_size) final; | ||||||
|  | 
 | ||||||
|  |     bool can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) final; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     /**  Computes the IoProx checksum of the provided (decoded) data. */ | ||||||
|  |     uint8_t compute_checksum(const uint8_t* data, const uint8_t data_size); | ||||||
|  | }; | ||||||
| @ -25,10 +25,12 @@ void RfidReader::decode(bool polarity) { | |||||||
|     case Type::Normal: |     case Type::Normal: | ||||||
|         decoder_em.process_front(polarity, period); |         decoder_em.process_front(polarity, period); | ||||||
|         decoder_hid26.process_front(polarity, period); |         decoder_hid26.process_front(polarity, period); | ||||||
|  |         decoder_ioprox.process_front(polarity, period); | ||||||
|         break; |         break; | ||||||
|     case Type::Indala: |     case Type::Indala: | ||||||
|         decoder_em.process_front(polarity, period); |         decoder_em.process_front(polarity, period); | ||||||
|         decoder_hid26.process_front(polarity, period); |         decoder_hid26.process_front(polarity, period); | ||||||
|  |         decoder_ioprox.process_front(polarity, period); | ||||||
|         decoder_indala.process_front(polarity, period); |         decoder_indala.process_front(polarity, period); | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
| @ -110,6 +112,11 @@ bool RfidReader::read(LfrfidKeyType* _type, uint8_t* data, uint8_t data_size, bo | |||||||
|         something_read = true; |         something_read = true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if(decoder_ioprox.read(data, data_size)) { | ||||||
|  |         *_type = LfrfidKeyType::KeyIoProxXSF; | ||||||
|  |         something_read = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     if(decoder_indala.read(data, data_size)) { |     if(decoder_indala.read(data, data_size)) { | ||||||
|         *_type = LfrfidKeyType::KeyI40134; |         *_type = LfrfidKeyType::KeyI40134; | ||||||
|         something_read = true; |         something_read = true; | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ | |||||||
| #include "decoder_emmarin.h" | #include "decoder_emmarin.h" | ||||||
| #include "decoder_hid26.h" | #include "decoder_hid26.h" | ||||||
| #include "decoder_indala.h" | #include "decoder_indala.h" | ||||||
|  | #include "decoder_ioprox.h" | ||||||
| #include "key_info.h" | #include "key_info.h" | ||||||
| 
 | 
 | ||||||
| //#define RFID_GPIO_DEBUG 1
 | //#define RFID_GPIO_DEBUG 1
 | ||||||
| @ -34,6 +35,7 @@ private: | |||||||
|     DecoderEMMarin decoder_em; |     DecoderEMMarin decoder_em; | ||||||
|     DecoderHID26 decoder_hid26; |     DecoderHID26 decoder_hid26; | ||||||
|     DecoderIndala decoder_indala; |     DecoderIndala decoder_indala; | ||||||
|  |     DecoderIoProx decoder_ioprox; | ||||||
| 
 | 
 | ||||||
|     uint32_t last_dwt_value; |     uint32_t last_dwt_value; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ | |||||||
| #include "encoder_emmarin.h" | #include "encoder_emmarin.h" | ||||||
| #include "encoder_hid_h10301.h" | #include "encoder_hid_h10301.h" | ||||||
| #include "encoder_indala_40134.h" | #include "encoder_indala_40134.h" | ||||||
|  | #include "encoder_ioprox.h" | ||||||
| #include "pulse_joiner.h" | #include "pulse_joiner.h" | ||||||
| #include <map> | #include <map> | ||||||
| 
 | 
 | ||||||
| @ -22,6 +23,7 @@ private: | |||||||
|         {LfrfidKeyType::KeyEM4100, new EncoderEM()}, |         {LfrfidKeyType::KeyEM4100, new EncoderEM()}, | ||||||
|         {LfrfidKeyType::KeyH10301, new EncoderHID_H10301()}, |         {LfrfidKeyType::KeyH10301, new EncoderHID_H10301()}, | ||||||
|         {LfrfidKeyType::KeyI40134, new EncoderIndala_40134()}, |         {LfrfidKeyType::KeyI40134, new EncoderIndala_40134()}, | ||||||
|  |         {LfrfidKeyType::KeyIoProxXSF, new EncoderIoProx()}, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     PulseJoiner pulse_joiner; |     PulseJoiner pulse_joiner; | ||||||
|  | |||||||
| @ -84,6 +84,11 @@ void RfidWorker::sq_write() { | |||||||
|             writer.write_indala(key.get_data()); |             writer.write_indala(key.get_data()); | ||||||
|             writer.stop(); |             writer.stop(); | ||||||
|             break; |             break; | ||||||
|  |         case LfrfidKeyType::KeyIoProxXSF: | ||||||
|  |             writer.start(); | ||||||
|  |             writer.write_ioprox(key.get_data()); | ||||||
|  |             writer.stop(); | ||||||
|  |             break; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -92,6 +97,7 @@ void RfidWorker::sq_write_start_validate() { | |||||||
|     switch(key.get_type()) { |     switch(key.get_type()) { | ||||||
|     case LfrfidKeyType::KeyEM4100: |     case LfrfidKeyType::KeyEM4100: | ||||||
|     case LfrfidKeyType::KeyH10301: |     case LfrfidKeyType::KeyH10301: | ||||||
|  |     case LfrfidKeyType::KeyIoProxXSF: | ||||||
|         reader.start_forced(RfidReader::Type::Normal); |         reader.start_forced(RfidReader::Type::Normal); | ||||||
|         break; |         break; | ||||||
|     case LfrfidKeyType::KeyI40134: |     case LfrfidKeyType::KeyI40134: | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "rfid_writer.h" | #include "rfid_writer.h" | ||||||
|  | #include "protocols/protocol_ioprox.h" | ||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
| #include "protocols/protocol_emmarin.h" | #include "protocols/protocol_emmarin.h" | ||||||
| #include "protocols/protocol_hid_h10301.h" | #include "protocols/protocol_hid_h10301.h" | ||||||
| @ -143,6 +144,28 @@ void RfidWriter::write_hid(const uint8_t hid_data[3]) { | |||||||
|     FURI_CRITICAL_EXIT(); |     FURI_CRITICAL_EXIT(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /** Endian fixup. Translates an ioprox block into a t5577 block */ | ||||||
|  | static uint32_t ioprox_encode_block(const uint8_t block_data[4]) { | ||||||
|  |     uint8_t raw_card_data[] = {block_data[3], block_data[2], block_data[1], block_data[0]}; | ||||||
|  |     return *reinterpret_cast<uint32_t*>(&raw_card_data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void RfidWriter::write_ioprox(const uint8_t ioprox_data[4]) { | ||||||
|  |     ProtocolIoProx ioprox_card; | ||||||
|  | 
 | ||||||
|  |     uint8_t encoded_data[8]; | ||||||
|  |     ioprox_card.encode(ioprox_data, 4, encoded_data, sizeof(encoded_data)); | ||||||
|  | 
 | ||||||
|  |     const uint32_t ioprox_config_block_data = 0b00000000000101000111000001000000; | ||||||
|  | 
 | ||||||
|  |     FURI_CRITICAL_ENTER(); | ||||||
|  |     write_block(0, 0, false, ioprox_config_block_data); | ||||||
|  |     write_block(0, 1, false, ioprox_encode_block(&encoded_data[0])); | ||||||
|  |     write_block(0, 2, false, ioprox_encode_block(&encoded_data[4])); | ||||||
|  |     write_reset(); | ||||||
|  |     FURI_CRITICAL_EXIT(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void RfidWriter::write_indala(const uint8_t indala_data[3]) { | void RfidWriter::write_indala(const uint8_t indala_data[3]) { | ||||||
|     ProtocolIndala40134 indala_card; |     ProtocolIndala40134 indala_card; | ||||||
|     uint32_t card_data[2]; |     uint32_t card_data[2]; | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ public: | |||||||
|     void stop(); |     void stop(); | ||||||
|     void write_em(const uint8_t em_data[5]); |     void write_em(const uint8_t em_data[5]); | ||||||
|     void write_hid(const uint8_t hid_data[3]); |     void write_hid(const uint8_t hid_data[3]); | ||||||
|  |     void write_ioprox(const uint8_t ioprox_data[4]); | ||||||
|     void write_indala(const uint8_t indala_data[3]); |     void write_indala(const uint8_t indala_data[3]); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|  | |||||||
| @ -1,4 +1,7 @@ | |||||||
| #include "lfrfid_app.h" | #include "lfrfid_app.h" | ||||||
|  | #include "assets_icons.h" | ||||||
|  | #include "furi/common_defines.h" | ||||||
|  | #include "m-string.h" | ||||||
| #include "scene/lfrfid_app_scene_start.h" | #include "scene/lfrfid_app_scene_start.h" | ||||||
| #include "scene/lfrfid_app_scene_read.h" | #include "scene/lfrfid_app_scene_read.h" | ||||||
| #include "scene/lfrfid_app_scene_read_success.h" | #include "scene/lfrfid_app_scene_read_success.h" | ||||||
| @ -31,9 +34,11 @@ LfRfidApp::LfRfidApp() | |||||||
|     , storage{"storage"} |     , storage{"storage"} | ||||||
|     , dialogs{"dialogs"} |     , dialogs{"dialogs"} | ||||||
|     , text_store(40) { |     , text_store(40) { | ||||||
|  |     string_init_set_str(file_path, app_folder); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| LfRfidApp::~LfRfidApp() { | LfRfidApp::~LfRfidApp() { | ||||||
|  |     string_clear(file_path); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void LfRfidApp::run(void* _args) { | void LfRfidApp::run(void* _args) { | ||||||
| @ -42,7 +47,8 @@ void LfRfidApp::run(void* _args) { | |||||||
|     make_app_folder(); |     make_app_folder(); | ||||||
| 
 | 
 | ||||||
|     if(strlen(args)) { |     if(strlen(args)) { | ||||||
|         load_key_data(args, &worker.key); |         string_set_str(file_path, args); | ||||||
|  |         load_key_data(file_path, &worker.key); | ||||||
|         scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); |         scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); | ||||||
|         scene_controller.process(100, SceneType::Emulate); |         scene_controller.process(100, SceneType::Emulate); | ||||||
|     } else { |     } else { | ||||||
| @ -69,65 +75,49 @@ void LfRfidApp::run(void* _args) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool LfRfidApp::save_key(RfidKey* key) { | bool LfRfidApp::save_key(RfidKey* key) { | ||||||
|     string_t file_name; |  | ||||||
|     bool result = false; |     bool result = false; | ||||||
| 
 | 
 | ||||||
|     make_app_folder(); |     make_app_folder(); | ||||||
| 
 | 
 | ||||||
|     string_init_printf(file_name, "%s/%s%s", app_folder, key->get_name(), app_extension); |     if(string_end_with_str_p(file_path, app_extension)) { | ||||||
|     result = save_key_data(string_get_cstr(file_name), key); |         size_t filename_start = string_search_rchar(file_path, '/'); | ||||||
|     string_clear(file_name); |         string_left(file_path, filename_start); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|  |     string_cat_printf(file_path, "/%s%s", key->get_name(), app_extension); | ||||||
|  | 
 | ||||||
|  |     result = save_key_data(file_path, key); | ||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool LfRfidApp::load_key_from_file_select(bool need_restore) { | bool LfRfidApp::load_key_from_file_select(bool need_restore) { | ||||||
|     TextStore* filename_ts = new TextStore(64); |     if(!need_restore) { | ||||||
|     bool result = false; |         string_set_str(file_path, app_folder); | ||||||
| 
 |  | ||||||
|     if(need_restore) { |  | ||||||
|         result = dialog_file_select_show( |  | ||||||
|             dialogs, |  | ||||||
|             app_folder, |  | ||||||
|             app_extension, |  | ||||||
|             filename_ts->text, |  | ||||||
|             filename_ts->text_size, |  | ||||||
|             worker.key.get_name()); |  | ||||||
|     } else { |  | ||||||
|         result = dialog_file_select_show( |  | ||||||
|             dialogs, app_folder, app_extension, filename_ts->text, filename_ts->text_size, NULL); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     bool result = dialog_file_browser_show( | ||||||
|  |         dialogs, file_path, file_path, app_extension, true, &I_125_10px, true); | ||||||
|  | 
 | ||||||
|     if(result) { |     if(result) { | ||||||
|         string_t key_str; |         result = load_key_data(file_path, &worker.key); | ||||||
|         string_init_printf(key_str, "%s/%s%s", app_folder, filename_ts->text, app_extension); |  | ||||||
|         result = load_key_data(string_get_cstr(key_str), &worker.key); |  | ||||||
|         string_clear(key_str); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     delete filename_ts; |  | ||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool LfRfidApp::delete_key(RfidKey* key) { | bool LfRfidApp::delete_key(RfidKey* key) { | ||||||
|     string_t file_name; |     UNUSED(key); | ||||||
|     bool result = false; |     return storage_simply_remove(storage, string_get_cstr(file_path)); | ||||||
| 
 |  | ||||||
|     string_init_printf(file_name, "%s/%s%s", app_folder, key->get_name(), app_extension); |  | ||||||
|     result = storage_simply_remove(storage, string_get_cstr(file_name)); |  | ||||||
|     string_clear(file_name); |  | ||||||
| 
 |  | ||||||
|     return result; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { | bool LfRfidApp::load_key_data(string_t path, RfidKey* key) { | ||||||
|     FlipperFormat* file = flipper_format_file_alloc(storage); |     FlipperFormat* file = flipper_format_file_alloc(storage); | ||||||
|     bool result = false; |     bool result = false; | ||||||
|     string_t str_result; |     string_t str_result; | ||||||
|     string_init(str_result); |     string_init(str_result); | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         if(!flipper_format_file_open_existing(file, path)) break; |         if(!flipper_format_file_open_existing(file, string_get_cstr(path))) break; | ||||||
| 
 | 
 | ||||||
|         // header
 |         // header
 | ||||||
|         uint32_t version; |         uint32_t version; | ||||||
| @ -149,7 +139,7 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { | |||||||
|             break; |             break; | ||||||
|         loaded_key.set_data(key_data, loaded_key.get_type_data_count()); |         loaded_key.set_data(key_data, loaded_key.get_type_data_count()); | ||||||
| 
 | 
 | ||||||
|         path_extract_filename_no_ext(path, str_result); |         path_extract_filename(path, str_result, true); | ||||||
|         loaded_key.set_name(string_get_cstr(str_result)); |         loaded_key.set_name(string_get_cstr(str_result)); | ||||||
| 
 | 
 | ||||||
|         *key = loaded_key; |         *key = loaded_key; | ||||||
| @ -166,12 +156,12 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { | |||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool LfRfidApp::save_key_data(const char* path, RfidKey* key) { | bool LfRfidApp::save_key_data(string_t path, RfidKey* key) { | ||||||
|     FlipperFormat* file = flipper_format_file_alloc(storage); |     FlipperFormat* file = flipper_format_file_alloc(storage); | ||||||
|     bool result = false; |     bool result = false; | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         if(!flipper_format_file_open_always(file, path)) break; |         if(!flipper_format_file_open_always(file, string_get_cstr(path))) break; | ||||||
|         if(!flipper_format_write_header_cstr(file, app_filetype, 1)) break; |         if(!flipper_format_write_header_cstr(file, app_filetype, 1)) break; | ||||||
|         if(!flipper_format_write_comment_cstr(file, "Key type can be EM4100, H10301 or I40134")) |         if(!flipper_format_write_comment_cstr(file, "Key type can be EM4100, H10301 or I40134")) | ||||||
|             break; |             break; | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  | #include "m-string.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| @ -76,6 +77,8 @@ public: | |||||||
| 
 | 
 | ||||||
|     TextStore text_store; |     TextStore text_store; | ||||||
| 
 | 
 | ||||||
|  |     string_t file_path; | ||||||
|  | 
 | ||||||
|     void run(void* args); |     void run(void* args); | ||||||
| 
 | 
 | ||||||
|     static const char* app_folder; |     static const char* app_folder; | ||||||
| @ -86,8 +89,8 @@ public: | |||||||
|     bool load_key_from_file_select(bool need_restore); |     bool load_key_from_file_select(bool need_restore); | ||||||
|     bool delete_key(RfidKey* key); |     bool delete_key(RfidKey* key); | ||||||
| 
 | 
 | ||||||
|     bool load_key_data(const char* path, RfidKey* key); |     bool load_key_data(string_t path, RfidKey* key); | ||||||
|     bool save_key_data(const char* path, RfidKey* key); |     bool save_key_data(string_t path, RfidKey* key); | ||||||
| 
 | 
 | ||||||
|     void make_app_folder(); |     void make_app_folder(); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -28,6 +28,7 @@ void lfrfid_cli_print_usage() { | |||||||
|     printf("\tEM4100, EM-Marin (5 bytes key_data)\r\n"); |     printf("\tEM4100, EM-Marin (5 bytes key_data)\r\n"); | ||||||
|     printf("\tH10301, HID26 (3 bytes key_data)\r\n"); |     printf("\tH10301, HID26 (3 bytes key_data)\r\n"); | ||||||
|     printf("\tI40134, Indala (3 bytes key_data)\r\n"); |     printf("\tI40134, Indala (3 bytes key_data)\r\n"); | ||||||
|  |     printf("\tIoProxXSF, IoProx (4 bytes key_data)\r\n"); | ||||||
|     printf("\t<key_data> are hex-formatted\r\n"); |     printf("\t<key_data> are hex-formatted\r\n"); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -43,6 +44,9 @@ static bool lfrfid_cli_get_key_type(string_t data, LfrfidKeyType* type) { | |||||||
|     } else if(string_cmp_str(data, "I40134") == 0 || string_cmp_str(data, "Indala") == 0) { |     } else if(string_cmp_str(data, "I40134") == 0 || string_cmp_str(data, "Indala") == 0) { | ||||||
|         result = true; |         result = true; | ||||||
|         *type = LfrfidKeyType::KeyI40134; |         *type = LfrfidKeyType::KeyI40134; | ||||||
|  |     } else if(string_cmp_str(data, "IoProxXSF") == 0 || string_cmp_str(data, "IoProx") == 0) { | ||||||
|  |         result = true; | ||||||
|  |         *type = LfrfidKeyType::KeyIoProxXSF; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return result; |     return result; | ||||||
|  | |||||||
| @ -50,6 +50,14 @@ void LfRfidAppSceneDeleteConfirm::on_enter(LfRfidApp* app, bool /* need_restore | |||||||
|         string_printf( |         string_printf( | ||||||
|             string_decrypted, "FC: %u    ID: %u", data[0], (uint16_t)((data[1] << 8) | (data[2]))); |             string_decrypted, "FC: %u    ID: %u", data[0], (uint16_t)((data[1] << 8) | (data[2]))); | ||||||
|         break; |         break; | ||||||
|  |     case LfrfidKeyType::KeyIoProxXSF: | ||||||
|  |         string_printf( | ||||||
|  |             string_decrypted, | ||||||
|  |             "FC: %u   VC: %u   ID: %u", | ||||||
|  |             data[0], | ||||||
|  |             data[1], | ||||||
|  |             (uint16_t)((data[2] << 8) | (data[3]))); | ||||||
|  |         break; | ||||||
|     } |     } | ||||||
|     line_3->set_text( |     line_3->set_text( | ||||||
|         string_get_cstr(string_decrypted), 64, 39, 0, AlignCenter, AlignBottom, FontSecondary); |         string_get_cstr(string_decrypted), 64, 39, 0, AlignCenter, AlignBottom, FontSecondary); | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ | |||||||
|     string_init(string[0]); |     string_init(string[0]); | ||||||
|     string_init(string[1]); |     string_init(string[1]); | ||||||
|     string_init(string[2]); |     string_init(string[2]); | ||||||
|  |     string_init(string[3]); | ||||||
| 
 | 
 | ||||||
|     auto container = app->view_controller.get<ContainerVM>(); |     auto container = app->view_controller.get<ContainerVM>(); | ||||||
| 
 | 
 | ||||||
| @ -25,11 +26,13 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ | |||||||
|     header->set_text(app->worker.key.get_type_text(), 89, 3, 0, AlignCenter); |     header->set_text(app->worker.key.get_type_text(), 89, 3, 0, AlignCenter); | ||||||
| 
 | 
 | ||||||
|     auto line_1_text = container->add<StringElement>(); |     auto line_1_text = container->add<StringElement>(); | ||||||
|     auto line_2_text = container->add<StringElement>(); |     auto line_2l_text = container->add<StringElement>(); | ||||||
|  |     auto line_2r_text = container->add<StringElement>(); | ||||||
|     auto line_3_text = container->add<StringElement>(); |     auto line_3_text = container->add<StringElement>(); | ||||||
| 
 | 
 | ||||||
|     auto line_1_value = container->add<StringElement>(); |     auto line_1_value = container->add<StringElement>(); | ||||||
|     auto line_2_value = container->add<StringElement>(); |     auto line_2l_value = container->add<StringElement>(); | ||||||
|  |     auto line_2r_value = container->add<StringElement>(); | ||||||
|     auto line_3_value = container->add<StringElement>(); |     auto line_3_value = container->add<StringElement>(); | ||||||
| 
 | 
 | ||||||
|     const uint8_t* data = app->worker.key.get_data(); |     const uint8_t* data = app->worker.key.get_data(); | ||||||
| @ -37,7 +40,7 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ | |||||||
|     switch(app->worker.key.get_type()) { |     switch(app->worker.key.get_type()) { | ||||||
|     case LfrfidKeyType::KeyEM4100: |     case LfrfidKeyType::KeyEM4100: | ||||||
|         line_1_text->set_text("HEX:", 65, 23, 0, AlignRight, AlignBottom, FontSecondary); |         line_1_text->set_text("HEX:", 65, 23, 0, AlignRight, AlignBottom, FontSecondary); | ||||||
|         line_2_text->set_text("Mod:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary); |         line_2l_text->set_text("Mod:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary); | ||||||
|         line_3_text->set_text("ID:", 65, 47, 0, AlignRight, AlignBottom, FontSecondary); |         line_3_text->set_text("ID:", 65, 47, 0, AlignRight, AlignBottom, FontSecondary); | ||||||
| 
 | 
 | ||||||
|         for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) { |         for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) { | ||||||
| @ -49,7 +52,7 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ | |||||||
| 
 | 
 | ||||||
|         line_1_value->set_text( |         line_1_value->set_text( | ||||||
|             string_get_cstr(string[0]), 68, 23, 0, AlignLeft, AlignBottom, FontSecondary); |             string_get_cstr(string[0]), 68, 23, 0, AlignLeft, AlignBottom, FontSecondary); | ||||||
|         line_2_value->set_text( |         line_2l_value->set_text( | ||||||
|             string_get_cstr(string[1]), 68, 35, 0, AlignLeft, AlignBottom, FontSecondary); |             string_get_cstr(string[1]), 68, 35, 0, AlignLeft, AlignBottom, FontSecondary); | ||||||
|         line_3_value->set_text( |         line_3_value->set_text( | ||||||
|             string_get_cstr(string[2]), 68, 47, 0, AlignLeft, AlignBottom, FontSecondary); |             string_get_cstr(string[2]), 68, 47, 0, AlignLeft, AlignBottom, FontSecondary); | ||||||
| @ -57,7 +60,7 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ | |||||||
|     case LfrfidKeyType::KeyH10301: |     case LfrfidKeyType::KeyH10301: | ||||||
|     case LfrfidKeyType::KeyI40134: |     case LfrfidKeyType::KeyI40134: | ||||||
|         line_1_text->set_text("HEX:", 65, 23, 0, AlignRight, AlignBottom, FontSecondary); |         line_1_text->set_text("HEX:", 65, 23, 0, AlignRight, AlignBottom, FontSecondary); | ||||||
|         line_2_text->set_text("FC:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary); |         line_2l_text->set_text("FC:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary); | ||||||
|         line_3_text->set_text("Card:", 65, 47, 0, AlignRight, AlignBottom, FontSecondary); |         line_3_text->set_text("Card:", 65, 47, 0, AlignRight, AlignBottom, FontSecondary); | ||||||
| 
 | 
 | ||||||
|         for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) { |         for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) { | ||||||
| @ -69,11 +72,36 @@ void LfRfidAppSceneReadSuccess::on_enter(LfRfidApp* app, bool /* need_restore */ | |||||||
| 
 | 
 | ||||||
|         line_1_value->set_text( |         line_1_value->set_text( | ||||||
|             string_get_cstr(string[0]), 68, 23, 0, AlignLeft, AlignBottom, FontSecondary); |             string_get_cstr(string[0]), 68, 23, 0, AlignLeft, AlignBottom, FontSecondary); | ||||||
|         line_2_value->set_text( |         line_2l_value->set_text( | ||||||
|             string_get_cstr(string[1]), 68, 35, 0, AlignLeft, AlignBottom, FontSecondary); |             string_get_cstr(string[1]), 68, 35, 0, AlignLeft, AlignBottom, FontSecondary); | ||||||
|         line_3_value->set_text( |         line_3_value->set_text( | ||||||
|             string_get_cstr(string[2]), 68, 47, 0, AlignLeft, AlignBottom, FontSecondary); |             string_get_cstr(string[2]), 68, 47, 0, AlignLeft, AlignBottom, FontSecondary); | ||||||
|         break; |         break; | ||||||
|  | 
 | ||||||
|  |     case LfrfidKeyType::KeyIoProxXSF: | ||||||
|  |         line_1_text->set_text("HEX:", 65, 23, 0, AlignRight, AlignBottom, FontSecondary); | ||||||
|  |         line_2l_text->set_text("FC:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary); | ||||||
|  |         line_2r_text->set_text("VС:", 95, 35, 0, AlignRight, AlignBottom, FontSecondary); | ||||||
|  |         line_3_text->set_text("Card:", 65, 47, 0, AlignRight, AlignBottom, FontSecondary); | ||||||
|  | 
 | ||||||
|  |         for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) { | ||||||
|  |             string_cat_printf(string[0], "%02X", data[i]); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         string_printf(string[1], "%u", data[0]); | ||||||
|  |         string_printf(string[2], "%u", (uint16_t)((data[2] << 8) | (data[3]))); | ||||||
|  |         string_printf(string[3], "%u", data[1]); | ||||||
|  | 
 | ||||||
|  |         line_1_value->set_text( | ||||||
|  |             string_get_cstr(string[0]), 68, 23, 0, AlignLeft, AlignBottom, FontSecondary); | ||||||
|  |         line_2l_value->set_text( | ||||||
|  |             string_get_cstr(string[1]), 68, 35, 0, AlignLeft, AlignBottom, FontSecondary); | ||||||
|  |         line_2r_value->set_text( | ||||||
|  |             string_get_cstr(string[3]), 98, 35, 0, AlignLeft, AlignBottom, FontSecondary); | ||||||
|  |         line_3_value->set_text( | ||||||
|  |             string_get_cstr(string[2]), 68, 47, 0, AlignLeft, AlignBottom, FontSecondary); | ||||||
|  | 
 | ||||||
|  |         break; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     app->view_controller.switch_to<ContainerVM>(); |     app->view_controller.switch_to<ContainerVM>(); | ||||||
|  | |||||||
| @ -1,23 +1,10 @@ | |||||||
| #include "lfrfid_app_scene_save_data.h" | #include "lfrfid_app_scene_save_data.h" | ||||||
| #include <dolphin/dolphin.h> | #include <dolphin/dolphin.h> | ||||||
| 
 | 
 | ||||||
| static void print_buffer(const uint8_t* buffer) { |  | ||||||
|     for(uint8_t i = 0; i < LFRFID_KEY_SIZE; i++) { |  | ||||||
|         printf("%02X", buffer[i]); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void LfRfidAppSceneSaveData::on_enter(LfRfidApp* app, bool need_restore) { | void LfRfidAppSceneSaveData::on_enter(LfRfidApp* app, bool need_restore) { | ||||||
|     auto byte_input = app->view_controller.get<ByteInputVM>(); |     auto byte_input = app->view_controller.get<ByteInputVM>(); | ||||||
|     RfidKey& key = app->worker.key; |     RfidKey& key = app->worker.key; | ||||||
| 
 | 
 | ||||||
|     printf("k: "); |  | ||||||
|     print_buffer(key.get_data()); |  | ||||||
|     printf(" o: "); |  | ||||||
|     print_buffer(old_key_data); |  | ||||||
|     printf(" n: "); |  | ||||||
|     print_buffer(new_key_data); |  | ||||||
|     printf("\r\n"); |  | ||||||
|     if(need_restore) printf("restored\r\n"); |     if(need_restore) printf("restored\r\n"); | ||||||
| 
 | 
 | ||||||
|     if(need_restore) { |     if(need_restore) { | ||||||
|  | |||||||
| @ -1,11 +1,14 @@ | |||||||
| #include "lfrfid_app_scene_save_name.h" | #include "lfrfid_app_scene_save_name.h" | ||||||
|  | #include "m-string.h" | ||||||
| #include <lib/toolbox/random_name.h> | #include <lib/toolbox/random_name.h> | ||||||
|  | #include <lib/toolbox/path.h> | ||||||
| 
 | 
 | ||||||
| void LfRfidAppSceneSaveName::on_enter(LfRfidApp* app, bool /* need_restore */) { | void LfRfidAppSceneSaveName::on_enter(LfRfidApp* app, bool /* need_restore */) { | ||||||
|     const char* key_name = app->worker.key.get_name(); |     const char* key_name = app->worker.key.get_name(); | ||||||
| 
 | 
 | ||||||
|     bool key_name_empty = !strcmp(key_name, ""); |     bool key_name_empty = !strcmp(key_name, ""); | ||||||
|     if(key_name_empty) { |     if(key_name_empty) { | ||||||
|  |         string_set_str(app->file_path, app->app_folder); | ||||||
|         set_random_name(app->text_store.text, app->text_store.text_size); |         set_random_name(app->text_store.text, app->text_store.text_size); | ||||||
|     } else { |     } else { | ||||||
|         app->text_store.set("%s", key_name); |         app->text_store.set("%s", key_name); | ||||||
| @ -21,10 +24,17 @@ void LfRfidAppSceneSaveName::on_enter(LfRfidApp* app, bool /* need_restore */) { | |||||||
|         app->worker.key.get_name_length(), |         app->worker.key.get_name_length(), | ||||||
|         key_name_empty); |         key_name_empty); | ||||||
| 
 | 
 | ||||||
|  |     string_t folder_path; | ||||||
|  |     string_init(folder_path); | ||||||
|  | 
 | ||||||
|  |     path_extract_dirname(string_get_cstr(app->file_path), folder_path); | ||||||
|  | 
 | ||||||
|     ValidatorIsFile* validator_is_file = |     ValidatorIsFile* validator_is_file = | ||||||
|         validator_is_file_alloc_init(app->app_folder, app->app_extension, key_name); |         validator_is_file_alloc_init(string_get_cstr(folder_path), app->app_extension, key_name); | ||||||
|     text_input->set_validator(validator_is_file_callback, validator_is_file); |     text_input->set_validator(validator_is_file_callback, validator_is_file); | ||||||
| 
 | 
 | ||||||
|  |     string_clear(folder_path); | ||||||
|  | 
 | ||||||
|     app->view_controller.switch_to<TextInputVM>(); |     app->view_controller.switch_to<TextInputVM>(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -10,6 +10,6 @@ public: | |||||||
| private: | private: | ||||||
|     static void submenu_callback(void* context, uint32_t index); |     static void submenu_callback(void* context, uint32_t index); | ||||||
|     uint32_t submenu_item_selected = 0; |     uint32_t submenu_item_selected = 0; | ||||||
|     static const uint8_t keys_count = static_cast<uint8_t>(LfrfidKeyType::KeyI40134); |     static const uint8_t keys_count = static_cast<uint8_t>(LfrfidKeyType::KeyIoProxXSF); | ||||||
|     string_t submenu_name[keys_count + 1]; |     string_t submenu_name[keys_count + 1]; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -43,6 +43,14 @@ void LfRfidAppSceneSavedInfo::on_enter(LfRfidApp* app, bool /* need_restore */) | |||||||
|         string_printf( |         string_printf( | ||||||
|             string_decrypted, "FC: %u    ID: %u", data[0], (uint16_t)((data[1] << 8) | (data[2]))); |             string_decrypted, "FC: %u    ID: %u", data[0], (uint16_t)((data[1] << 8) | (data[2]))); | ||||||
|         break; |         break; | ||||||
|  |     case LfrfidKeyType::KeyIoProxXSF: | ||||||
|  |         string_printf( | ||||||
|  |             string_decrypted, | ||||||
|  |             "FC: %u   VC: %u   ID: %u", | ||||||
|  |             data[0], | ||||||
|  |             data[1], | ||||||
|  |             (uint16_t)((data[2] << 8) | (data[3]))); | ||||||
|  |         break; | ||||||
|     } |     } | ||||||
|     line_3->set_text( |     line_3->set_text( | ||||||
|         string_get_cstr(string_decrypted), 64, 39, 0, AlignCenter, AlignBottom, FontSecondary); |         string_get_cstr(string_decrypted), 64, 39, 0, AlignCenter, AlignBottom, FontSecondary); | ||||||
|  | |||||||
| @ -1,3 +1,5 @@ | |||||||
|  | #include "assets_icons.h" | ||||||
|  | #include "m-string.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| @ -298,23 +300,23 @@ int32_t music_player_app(void* p) { | |||||||
|         if(p) { |         if(p) { | ||||||
|             string_cat_str(file_path, p); |             string_cat_str(file_path, p); | ||||||
|         } else { |         } else { | ||||||
|             char file_name[256] = {0}; |             string_set_str(file_path, MUSIC_PLAYER_APP_PATH_FOLDER); | ||||||
|  | 
 | ||||||
|             DialogsApp* dialogs = furi_record_open("dialogs"); |             DialogsApp* dialogs = furi_record_open("dialogs"); | ||||||
|             bool res = dialog_file_select_show( |             bool res = dialog_file_browser_show( | ||||||
|                 dialogs, |                 dialogs, | ||||||
|                 MUSIC_PLAYER_APP_PATH_FOLDER, |                 file_path, | ||||||
|  |                 file_path, | ||||||
|                 MUSIC_PLAYER_APP_EXTENSION, |                 MUSIC_PLAYER_APP_EXTENSION, | ||||||
|                 file_name, |                 true, | ||||||
|                 255, |                 &I_music_10px, | ||||||
|                 NULL); |                 false); | ||||||
|  | 
 | ||||||
|             furi_record_close("dialogs"); |             furi_record_close("dialogs"); | ||||||
|             if(!res) { |             if(!res) { | ||||||
|                 FURI_LOG_E(TAG, "No file selected"); |                 FURI_LOG_E(TAG, "No file selected"); | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             string_cat_str(file_path, MUSIC_PLAYER_APP_PATH_FOLDER); |  | ||||||
|             string_cat_str(file_path, "/"); |  | ||||||
|             string_cat_str(file_path, file_name); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if(!music_player_worker_load(music_player->worker, string_get_cstr(file_path))) { |         if(!music_player_worker_load(music_player->worker, string_get_cstr(file_path))) { | ||||||
|  | |||||||
| @ -59,9 +59,10 @@ static int32_t music_player_worker_thread_callback(void* context) { | |||||||
|             float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4); |             float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4); | ||||||
|             float duration = |             float duration = | ||||||
|                 60.0 * osKernelGetTickFreq() * 4 / instance->bpm / note_block->duration; |                 60.0 * osKernelGetTickFreq() * 4 / instance->bpm / note_block->duration; | ||||||
|             while(note_block->dots > 0) { |             uint32_t dots = note_block->dots; | ||||||
|  |             while(dots > 0) { | ||||||
|                 duration += duration / 2; |                 duration += duration / 2; | ||||||
|                 note_block->dots--; |                 dots--; | ||||||
|             } |             } | ||||||
|             uint32_t next_tick = furi_hal_get_tick() + duration; |             uint32_t next_tick = furi_hal_get_tick() + duration; | ||||||
|             float volume = instance->volume; |             float volume = instance->volume; | ||||||
|  | |||||||
| @ -173,6 +173,8 @@ int32_t nfc_app(void* p) { | |||||||
|         if(nfc_device_load(nfc->dev, p)) { |         if(nfc_device_load(nfc->dev, p)) { | ||||||
|             if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { |             if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { | ||||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl); |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl); | ||||||
|  |             } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { | ||||||
|  |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareClassic); | ||||||
|             } else { |             } else { | ||||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid); | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -1,4 +1,6 @@ | |||||||
| #include "nfc_device.h" | #include "nfc_device.h" | ||||||
|  | #include "assets_icons.h" | ||||||
|  | #include "m-string.h" | ||||||
| #include "nfc_types.h" | #include "nfc_types.h" | ||||||
| 
 | 
 | ||||||
| #include <toolbox/path.h> | #include <toolbox/path.h> | ||||||
| @ -7,10 +9,14 @@ | |||||||
| static const char* nfc_file_header = "Flipper NFC device"; | static const char* nfc_file_header = "Flipper NFC device"; | ||||||
| static const uint32_t nfc_file_version = 2; | static const uint32_t nfc_file_version = 2; | ||||||
| 
 | 
 | ||||||
|  | // Protocols format versions
 | ||||||
|  | static const uint32_t nfc_mifare_classic_data_format_version = 1; | ||||||
|  | 
 | ||||||
| NfcDevice* nfc_device_alloc() { | NfcDevice* nfc_device_alloc() { | ||||||
|     NfcDevice* nfc_dev = malloc(sizeof(NfcDevice)); |     NfcDevice* nfc_dev = malloc(sizeof(NfcDevice)); | ||||||
|     nfc_dev->storage = furi_record_open("storage"); |     nfc_dev->storage = furi_record_open("storage"); | ||||||
|     nfc_dev->dialogs = furi_record_open("dialogs"); |     nfc_dev->dialogs = furi_record_open("dialogs"); | ||||||
|  |     string_init(nfc_dev->load_path); | ||||||
|     return nfc_dev; |     return nfc_dev; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -19,6 +25,7 @@ void nfc_device_free(NfcDevice* nfc_dev) { | |||||||
|     nfc_device_clear(nfc_dev); |     nfc_device_clear(nfc_dev); | ||||||
|     furi_record_close("storage"); |     furi_record_close("storage"); | ||||||
|     furi_record_close("dialogs"); |     furi_record_close("dialogs"); | ||||||
|  |     string_clear(nfc_dev->load_path); | ||||||
|     free(nfc_dev); |     free(nfc_dev); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -192,6 +199,10 @@ static bool nfc_device_save_mifare_df_key_settings( | |||||||
|         string_printf(key, "%s Key Changeable", prefix); |         string_printf(key, "%s Key Changeable", prefix); | ||||||
|         if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->master_key_changeable, 1)) |         if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->master_key_changeable, 1)) | ||||||
|             break; |             break; | ||||||
|  |         if(ks->flags) { | ||||||
|  |             string_printf(key, "%s Flags", prefix); | ||||||
|  |             if(!flipper_format_write_hex(file, string_get_cstr(key), &ks->flags, 1)) break; | ||||||
|  |         } | ||||||
|         string_printf(key, "%s Max Keys", prefix); |         string_printf(key, "%s Max Keys", prefix); | ||||||
|         if(!flipper_format_write_hex(file, string_get_cstr(key), &ks->max_keys, 1)) break; |         if(!flipper_format_write_hex(file, string_get_cstr(key), &ks->max_keys, 1)) break; | ||||||
|         for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) { |         for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) { | ||||||
| @ -227,8 +238,14 @@ bool nfc_device_load_mifare_df_key_settings( | |||||||
|         string_printf(key, "%s Key Changeable", prefix); |         string_printf(key, "%s Key Changeable", prefix); | ||||||
|         if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->master_key_changeable, 1)) |         if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->master_key_changeable, 1)) | ||||||
|             break; |             break; | ||||||
|  |         string_printf(key, "%s Flags", prefix); | ||||||
|  |         if(flipper_format_key_exist(file, string_get_cstr(key))) { | ||||||
|  |             if(!flipper_format_read_hex(file, string_get_cstr(key), &ks->flags, 1)) break; | ||||||
|  |         } | ||||||
|         string_printf(key, "%s Max Keys", prefix); |         string_printf(key, "%s Max Keys", prefix); | ||||||
|         if(!flipper_format_read_hex(file, string_get_cstr(key), &ks->max_keys, 1)) break; |         if(!flipper_format_read_hex(file, string_get_cstr(key), &ks->max_keys, 1)) break; | ||||||
|  |         ks->flags |= ks->max_keys >> 4; | ||||||
|  |         ks->max_keys &= 0xF; | ||||||
|         MifareDesfireKeyVersion** kv_head = &ks->key_version_head; |         MifareDesfireKeyVersion** kv_head = &ks->key_version_head; | ||||||
|         for(int key_id = 0; key_id < ks->max_keys; key_id++) { |         for(int key_id = 0; key_id < ks->max_keys; key_id++) { | ||||||
|             string_printf(key, "%s Key %d Version", prefix, key_id); |             string_printf(key, "%s Key %d Version", prefix, key_id); | ||||||
| @ -624,6 +641,7 @@ static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice* | |||||||
|     // Save Mifare Classic specific data
 |     // Save Mifare Classic specific data
 | ||||||
|     do { |     do { | ||||||
|         if(!flipper_format_write_comment_cstr(file, "Mifare Classic specific data")) break; |         if(!flipper_format_write_comment_cstr(file, "Mifare Classic specific data")) break; | ||||||
|  | 
 | ||||||
|         if(data->type == MfClassicType1k) { |         if(data->type == MfClassicType1k) { | ||||||
|             if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break; |             if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break; | ||||||
|             blocks = 64; |             blocks = 64; | ||||||
| @ -631,8 +649,17 @@ static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice* | |||||||
|             if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "4K")) break; |             if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "4K")) break; | ||||||
|             blocks = 256; |             blocks = 256; | ||||||
|         } |         } | ||||||
|         if(!flipper_format_write_comment_cstr(file, "Mifare Classic blocks")) break; |         if(!flipper_format_write_uint32( | ||||||
|  |                file, "Data format version", &nfc_mifare_classic_data_format_version, 1)) | ||||||
|  |             break; | ||||||
| 
 | 
 | ||||||
|  |         if(!flipper_format_write_comment_cstr( | ||||||
|  |                file, "Key map is the bit mask indicating valid key in each sector")) | ||||||
|  |             break; | ||||||
|  |         if(!flipper_format_write_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break; | ||||||
|  |         if(!flipper_format_write_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break; | ||||||
|  | 
 | ||||||
|  |         if(!flipper_format_write_comment_cstr(file, "Mifare Classic blocks")) break; | ||||||
|         bool block_saved = true; |         bool block_saved = true; | ||||||
|         for(size_t i = 0; i < blocks; i++) { |         for(size_t i = 0; i < blocks; i++) { | ||||||
|             string_printf(temp_str, "Block %d", i); |             string_printf(temp_str, "Block %d", i); | ||||||
| @ -654,6 +681,7 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice* | |||||||
|     bool parsed = false; |     bool parsed = false; | ||||||
|     MfClassicData* data = &dev->dev_data.mf_classic_data; |     MfClassicData* data = &dev->dev_data.mf_classic_data; | ||||||
|     string_t temp_str; |     string_t temp_str; | ||||||
|  |     uint32_t data_format_version = 0; | ||||||
|     string_init(temp_str); |     string_init(temp_str); | ||||||
|     uint16_t data_blocks = 0; |     uint16_t data_blocks = 0; | ||||||
| 
 | 
 | ||||||
| @ -669,6 +697,19 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice* | |||||||
|         } else { |         } else { | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         // Read Mifare Classic format version
 | ||||||
|  |         if(!flipper_format_read_uint32(file, "Data format version", &data_format_version, 1)) { | ||||||
|  |             // Load unread sectors with zero keys access for backward compatability
 | ||||||
|  |             if(!flipper_format_rewind(file)) break; | ||||||
|  |             data->key_a_mask = 0xffffffffffffffff; | ||||||
|  |             data->key_b_mask = 0xffffffffffffffff; | ||||||
|  |         } else { | ||||||
|  |             if(data_format_version != nfc_mifare_classic_data_format_version) break; | ||||||
|  |             if(!flipper_format_read_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break; | ||||||
|  |             if(!flipper_format_read_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         // Read Mifare Classic blocks
 |         // Read Mifare Classic blocks
 | ||||||
|         bool block_read = true; |         bool block_read = true; | ||||||
|         for(size_t i = 0; i < data_blocks; i++) { |         for(size_t i = 0; i < data_blocks; i++) { | ||||||
| @ -693,11 +734,24 @@ void nfc_device_set_name(NfcDevice* dev, const char* name) { | |||||||
|     strlcpy(dev->dev_name, name, NFC_DEV_NAME_MAX_LEN); |     strlcpy(dev->dev_name, name, NFC_DEV_NAME_MAX_LEN); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void nfc_device_get_path_without_ext(string_t orig_path, string_t shadow_path) { | ||||||
|  |     // TODO: this won't work if there is ".nfc" anywhere in the path other than
 | ||||||
|  |     // at the end
 | ||||||
|  |     size_t ext_start = string_search_str(orig_path, NFC_APP_EXTENSION); | ||||||
|  |     string_set_n(shadow_path, orig_path, 0, ext_start); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void nfc_device_get_shadow_path(string_t orig_path, string_t shadow_path) { | ||||||
|  |     nfc_device_get_path_without_ext(orig_path, shadow_path); | ||||||
|  |     string_cat_printf(shadow_path, "%s", NFC_APP_SHADOW_EXTENSION); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static bool nfc_device_save_file( | static bool nfc_device_save_file( | ||||||
|     NfcDevice* dev, |     NfcDevice* dev, | ||||||
|     const char* dev_name, |     const char* dev_name, | ||||||
|     const char* folder, |     const char* folder, | ||||||
|     const char* extension) { |     const char* extension, | ||||||
|  |     bool use_load_path) { | ||||||
|     furi_assert(dev); |     furi_assert(dev); | ||||||
| 
 | 
 | ||||||
|     bool saved = false; |     bool saved = false; | ||||||
| @ -707,10 +761,19 @@ static bool nfc_device_save_file( | |||||||
|     string_init(temp_str); |     string_init(temp_str); | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         // Create nfc directory if necessary
 |         if(use_load_path && !string_empty_p(dev->load_path)) { | ||||||
|         if(!storage_simply_mkdir(dev->storage, NFC_APP_FOLDER)) break; |             // Get directory name
 | ||||||
|         // First remove nfc device file if it was saved
 |             path_extract_dirname(string_get_cstr(dev->load_path), temp_str); | ||||||
|         string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); |             // Create nfc directory if necessary
 | ||||||
|  |             if(!storage_simply_mkdir(dev->storage, string_get_cstr(temp_str))) break; | ||||||
|  |             // Make path to file to save
 | ||||||
|  |             string_cat_printf(temp_str, "/%s%s", dev_name, extension); | ||||||
|  |         } else { | ||||||
|  |             // Create nfc directory if necessary
 | ||||||
|  |             if(!storage_simply_mkdir(dev->storage, NFC_APP_FOLDER)) break; | ||||||
|  |             // First remove nfc device file if it was saved
 | ||||||
|  |             string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); | ||||||
|  |         } | ||||||
|         // Open file
 |         // Open file
 | ||||||
|         if(!flipper_format_file_open_always(file, string_get_cstr(temp_str))) break; |         if(!flipper_format_file_open_always(file, string_get_cstr(temp_str))) break; | ||||||
|         // Write header
 |         // Write header
 | ||||||
| @ -749,12 +812,12 @@ static bool nfc_device_save_file( | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_device_save(NfcDevice* dev, const char* dev_name) { | bool nfc_device_save(NfcDevice* dev, const char* dev_name) { | ||||||
|     return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_EXTENSION); |     return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_EXTENSION, true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name) { | bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name) { | ||||||
|     dev->shadow_file_exist = true; |     dev->shadow_file_exist = true; | ||||||
|     return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_SHADOW_EXTENSION); |     return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_SHADOW_EXTENSION, true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool nfc_device_load_data(NfcDevice* dev, string_t path) { | static bool nfc_device_load_data(NfcDevice* dev, string_t path) { | ||||||
| @ -768,9 +831,7 @@ static bool nfc_device_load_data(NfcDevice* dev, string_t path) { | |||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         // Check existance of shadow file
 |         // Check existance of shadow file
 | ||||||
|         size_t ext_start = string_search_str(path, NFC_APP_EXTENSION); |         nfc_device_get_shadow_path(path, temp_str); | ||||||
|         string_set_n(temp_str, path, 0, ext_start); |  | ||||||
|         string_cat_printf(temp_str, "%s", NFC_APP_SHADOW_EXTENSION); |  | ||||||
|         dev->shadow_file_exist = |         dev->shadow_file_exist = | ||||||
|             storage_common_stat(dev->storage, string_get_cstr(temp_str), NULL) == FSE_OK; |             storage_common_stat(dev->storage, string_get_cstr(temp_str), NULL) == FSE_OK; | ||||||
|         // Open shadow file if it exists. If not - open original
 |         // Open shadow file if it exists. If not - open original
 | ||||||
| @ -827,15 +888,16 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path) { | |||||||
|     furi_assert(file_path); |     furi_assert(file_path); | ||||||
| 
 | 
 | ||||||
|     // Load device data
 |     // Load device data
 | ||||||
|     string_t path; |     string_set_str(dev->load_path, file_path); | ||||||
|     string_init_set_str(path, file_path); |     bool dev_load = nfc_device_load_data(dev, dev->load_path); | ||||||
|     bool dev_load = nfc_device_load_data(dev, path); |  | ||||||
|     if(dev_load) { |     if(dev_load) { | ||||||
|         // Set device name
 |         // Set device name
 | ||||||
|         path_extract_filename_no_ext(file_path, path); |         string_t filename; | ||||||
|         nfc_device_set_name(dev, string_get_cstr(path)); |         string_init(filename); | ||||||
|  |         path_extract_filename_no_ext(file_path, filename); | ||||||
|  |         nfc_device_set_name(dev, string_get_cstr(filename)); | ||||||
|  |         string_clear(filename); | ||||||
|     } |     } | ||||||
|     string_clear(path); |  | ||||||
| 
 | 
 | ||||||
|     return dev_load; |     return dev_load; | ||||||
| } | } | ||||||
| @ -843,23 +905,22 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path) { | |||||||
| bool nfc_file_select(NfcDevice* dev) { | bool nfc_file_select(NfcDevice* dev) { | ||||||
|     furi_assert(dev); |     furi_assert(dev); | ||||||
| 
 | 
 | ||||||
|     // Input events and views are managed by file_select
 |     // Input events and views are managed by file_browser
 | ||||||
|     bool res = dialog_file_select_show( |     string_t nfc_app_folder; | ||||||
|         dev->dialogs, |     string_init_set_str(nfc_app_folder, NFC_APP_FOLDER); | ||||||
|         NFC_APP_FOLDER, |     bool res = dialog_file_browser_show( | ||||||
|         NFC_APP_EXTENSION, |         dev->dialogs, dev->load_path, nfc_app_folder, NFC_APP_EXTENSION, true, &I_Nfc_10px, true); | ||||||
|         dev->file_name, |     string_clear(nfc_app_folder); | ||||||
|         sizeof(dev->file_name), |  | ||||||
|         dev->dev_name); |  | ||||||
|     if(res) { |     if(res) { | ||||||
|         string_t dev_str; |         string_t filename; | ||||||
|         // Get key file path
 |         string_init(filename); | ||||||
|         string_init_printf(dev_str, "%s/%s%s", NFC_APP_FOLDER, dev->file_name, NFC_APP_EXTENSION); |         path_extract_filename(dev->load_path, filename, true); | ||||||
|         res = nfc_device_load_data(dev, dev_str); |         strncpy(dev->dev_name, string_get_cstr(filename), NFC_DEV_NAME_MAX_LEN); | ||||||
|  |         res = nfc_device_load_data(dev, dev->load_path); | ||||||
|         if(res) { |         if(res) { | ||||||
|             nfc_device_set_name(dev, dev->file_name); |             nfc_device_set_name(dev, dev->dev_name); | ||||||
|         } |         } | ||||||
|         string_clear(dev_str); |         string_clear(filename); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return res; |     return res; | ||||||
| @ -877,9 +938,10 @@ void nfc_device_clear(NfcDevice* dev) { | |||||||
|     nfc_device_data_clear(&dev->dev_data); |     nfc_device_data_clear(&dev->dev_data); | ||||||
|     memset(&dev->dev_data, 0, sizeof(dev->dev_data)); |     memset(&dev->dev_data, 0, sizeof(dev->dev_data)); | ||||||
|     dev->format = NfcDeviceSaveFormatUid; |     dev->format = NfcDeviceSaveFormatUid; | ||||||
|  |     string_reset(dev->load_path); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_device_delete(NfcDevice* dev) { | bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { | ||||||
|     furi_assert(dev); |     furi_assert(dev); | ||||||
| 
 | 
 | ||||||
|     bool deleted = false; |     bool deleted = false; | ||||||
| @ -888,12 +950,20 @@ bool nfc_device_delete(NfcDevice* dev) { | |||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         // Delete original file
 |         // Delete original file
 | ||||||
|         string_init_printf(file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); |         if(use_load_path && !string_empty_p(dev->load_path)) { | ||||||
|  |             string_set(file_path, dev->load_path); | ||||||
|  |         } else { | ||||||
|  |             string_printf(file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); | ||||||
|  |         } | ||||||
|         if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; |         if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; | ||||||
|         // Delete shadow file if it exists
 |         // Delete shadow file if it exists
 | ||||||
|         if(dev->shadow_file_exist) { |         if(dev->shadow_file_exist) { | ||||||
|             string_printf( |             if(use_load_path && !string_empty_p(dev->load_path)) { | ||||||
|                 file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); |                 nfc_device_get_shadow_path(dev->load_path, file_path); | ||||||
|  |             } else { | ||||||
|  |                 string_printf( | ||||||
|  |                     file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); | ||||||
|  |             } | ||||||
|             if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; |             if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; | ||||||
|         } |         } | ||||||
|         deleted = true; |         deleted = true; | ||||||
| @ -907,19 +977,29 @@ bool nfc_device_delete(NfcDevice* dev) { | |||||||
|     return deleted; |     return deleted; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_device_restore(NfcDevice* dev) { | bool nfc_device_restore(NfcDevice* dev, bool use_load_path) { | ||||||
|     furi_assert(dev); |     furi_assert(dev); | ||||||
|     furi_assert(dev->shadow_file_exist); |     furi_assert(dev->shadow_file_exist); | ||||||
| 
 | 
 | ||||||
|     bool restored = false; |     bool restored = false; | ||||||
|     string_t path; |     string_t path; | ||||||
| 
 | 
 | ||||||
|  |     string_init(path); | ||||||
|  | 
 | ||||||
|     do { |     do { | ||||||
|         string_init_printf( |         if(use_load_path && !string_empty_p(dev->load_path)) { | ||||||
|             path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); |             nfc_device_get_shadow_path(dev->load_path, path); | ||||||
|  |         } else { | ||||||
|  |             string_printf( | ||||||
|  |                 path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); | ||||||
|  |         } | ||||||
|         if(!storage_simply_remove(dev->storage, string_get_cstr(path))) break; |         if(!storage_simply_remove(dev->storage, string_get_cstr(path))) break; | ||||||
|         dev->shadow_file_exist = false; |         dev->shadow_file_exist = false; | ||||||
|         string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); |         if(use_load_path && !string_empty_p(dev->load_path)) { | ||||||
|  |             string_set(path, dev->load_path); | ||||||
|  |         } else { | ||||||
|  |             string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); | ||||||
|  |         } | ||||||
|         if(!nfc_device_load_data(dev, path)) break; |         if(!nfc_device_load_data(dev, path)) break; | ||||||
|         restored = true; |         restored = true; | ||||||
|     } while(0); |     } while(0); | ||||||
|  | |||||||
| @ -12,7 +12,6 @@ | |||||||
| #include <lib/nfc_protocols/mifare_desfire.h> | #include <lib/nfc_protocols/mifare_desfire.h> | ||||||
| 
 | 
 | ||||||
| #define NFC_DEV_NAME_MAX_LEN 22 | #define NFC_DEV_NAME_MAX_LEN 22 | ||||||
| #define NFC_FILE_NAME_MAX_LEN 120 |  | ||||||
| #define NFC_READER_DATA_MAX_SIZE 64 | #define NFC_READER_DATA_MAX_SIZE 64 | ||||||
| 
 | 
 | ||||||
| #define NFC_APP_FOLDER "/any/nfc" | #define NFC_APP_FOLDER "/any/nfc" | ||||||
| @ -57,7 +56,7 @@ typedef struct { | |||||||
|     DialogsApp* dialogs; |     DialogsApp* dialogs; | ||||||
|     NfcDeviceData dev_data; |     NfcDeviceData dev_data; | ||||||
|     char dev_name[NFC_DEV_NAME_MAX_LEN + 1]; |     char dev_name[NFC_DEV_NAME_MAX_LEN + 1]; | ||||||
|     char file_name[NFC_FILE_NAME_MAX_LEN]; |     string_t load_path; | ||||||
|     NfcDeviceSaveFormat format; |     NfcDeviceSaveFormat format; | ||||||
|     bool shadow_file_exist; |     bool shadow_file_exist; | ||||||
| } NfcDevice; | } NfcDevice; | ||||||
| @ -80,6 +79,6 @@ void nfc_device_data_clear(NfcDeviceData* dev); | |||||||
| 
 | 
 | ||||||
| void nfc_device_clear(NfcDevice* dev); | void nfc_device_clear(NfcDevice* dev); | ||||||
| 
 | 
 | ||||||
| bool nfc_device_delete(NfcDevice* dev); | bool nfc_device_delete(NfcDevice* dev, bool use_load_path); | ||||||
| 
 | 
 | ||||||
| bool nfc_device_restore(NfcDevice* dev); | bool nfc_device_restore(NfcDevice* dev, bool use_load_path); | ||||||
|  | |||||||
| @ -35,6 +35,14 @@ const char* nfc_mf_ul_type(MfUltralightType type, bool full_name) { | |||||||
|         return "NTAG215"; |         return "NTAG215"; | ||||||
|     } else if(type == MfUltralightTypeNTAG216) { |     } else if(type == MfUltralightTypeNTAG216) { | ||||||
|         return "NTAG216"; |         return "NTAG216"; | ||||||
|  |     } else if(type == MfUltralightTypeNTAGI2C1K) { | ||||||
|  |         return "NTAG I2C 1K"; | ||||||
|  |     } else if(type == MfUltralightTypeNTAGI2C2K) { | ||||||
|  |         return "NTAG I2C 2K"; | ||||||
|  |     } else if(type == MfUltralightTypeNTAGI2CPlus1K) { | ||||||
|  |         return "NTAG I2C Plus 1K"; | ||||||
|  |     } else if(type == MfUltralightTypeNTAGI2CPlus2K) { | ||||||
|  |         return "NTAG I2C Plus 2K"; | ||||||
|     } else if(type == MfUltralightTypeUL11 && full_name) { |     } else if(type == MfUltralightTypeUL11 && full_name) { | ||||||
|         return "Mifare Ultralight 11"; |         return "Mifare Ultralight 11"; | ||||||
|     } else if(type == MfUltralightTypeUL21 && full_name) { |     } else if(type == MfUltralightTypeUL21 && full_name) { | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ | |||||||
| #include <lib/nfc_protocols/mifare_ultralight.h> | #include <lib/nfc_protocols/mifare_ultralight.h> | ||||||
| #include <lib/nfc_protocols/mifare_classic.h> | #include <lib/nfc_protocols/mifare_classic.h> | ||||||
| #include <lib/nfc_protocols/mifare_desfire.h> | #include <lib/nfc_protocols/mifare_desfire.h> | ||||||
|  | #include <lib/nfc_protocols/nfca.h> | ||||||
| 
 | 
 | ||||||
| #include "helpers/nfc_mf_classic_dict.h" | #include "helpers/nfc_mf_classic_dict.h" | ||||||
| 
 | 
 | ||||||
| @ -104,6 +105,8 @@ int32_t nfc_worker_task(void* context) { | |||||||
|         nfc_worker_emulate_mifare_ul(nfc_worker); |         nfc_worker_emulate_mifare_ul(nfc_worker); | ||||||
|     } else if(nfc_worker->state == NfcWorkerStateReadMifareClassic) { |     } else if(nfc_worker->state == NfcWorkerStateReadMifareClassic) { | ||||||
|         nfc_worker_mifare_classic_dict_attack(nfc_worker); |         nfc_worker_mifare_classic_dict_attack(nfc_worker); | ||||||
|  |     } else if(nfc_worker->state == NfcWorkerStateEmulateMifareClassic) { | ||||||
|  |         nfc_worker_emulate_mifare_classic(nfc_worker); | ||||||
|     } else if(nfc_worker->state == NfcWorkerStateReadMifareDesfire) { |     } else if(nfc_worker->state == NfcWorkerStateReadMifareDesfire) { | ||||||
|         nfc_worker_read_mifare_desfire(nfc_worker); |         nfc_worker_read_mifare_desfire(nfc_worker); | ||||||
|     } |     } | ||||||
| @ -312,6 +315,11 @@ void nfc_worker_emulate_mifare_ul(NfcWorker* nfc_worker) { | |||||||
|     MfUltralightEmulator emulator = {}; |     MfUltralightEmulator emulator = {}; | ||||||
|     mf_ul_prepare_emulation(&emulator, &nfc_worker->dev_data->mf_ul_data); |     mf_ul_prepare_emulation(&emulator, &nfc_worker->dev_data->mf_ul_data); | ||||||
|     while(nfc_worker->state == NfcWorkerStateEmulateMifareUltralight) { |     while(nfc_worker->state == NfcWorkerStateEmulateMifareUltralight) { | ||||||
|  |         emulator.auth_success = false; | ||||||
|  |         if(emulator.data.type >= MfUltralightTypeNTAGI2C1K) { | ||||||
|  |             // Sector index needs to be reset
 | ||||||
|  |             emulator.curr_sector = 0; | ||||||
|  |         } | ||||||
|         furi_hal_nfc_emulate_nfca( |         furi_hal_nfc_emulate_nfca( | ||||||
|             nfc_data->uid, |             nfc_data->uid, | ||||||
|             nfc_data->uid_len, |             nfc_data->uid_len, | ||||||
| @ -474,6 +482,34 @@ void nfc_worker_mifare_classic_dict_attack(NfcWorker* nfc_worker) { | |||||||
|     stream_free(nfc_worker->dict_stream); |     stream_free(nfc_worker->dict_stream); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker) { | ||||||
|  |     FuriHalNfcTxRxContext tx_rx; | ||||||
|  |     FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; | ||||||
|  |     MfClassicEmulator emulator = { | ||||||
|  |         .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4), | ||||||
|  |         .data = nfc_worker->dev_data->mf_classic_data, | ||||||
|  |         .data_changed = false, | ||||||
|  |     }; | ||||||
|  |     NfcaSignal* nfca_signal = nfca_signal_alloc(); | ||||||
|  |     tx_rx.nfca_signal = nfca_signal; | ||||||
|  | 
 | ||||||
|  |     while(nfc_worker->state == NfcWorkerStateEmulateMifareClassic) { | ||||||
|  |         if(furi_hal_nfc_listen( | ||||||
|  |                nfc_data->uid, nfc_data->uid_len, nfc_data->atqa, nfc_data->sak, true, 300)) { | ||||||
|  |             mf_classic_emulator(&emulator, &tx_rx); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     if(emulator.data_changed) { | ||||||
|  |         nfc_worker->dev_data->mf_classic_data = emulator.data; | ||||||
|  |         if(nfc_worker->callback) { | ||||||
|  |             nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); | ||||||
|  |         } | ||||||
|  |         emulator.data_changed = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     nfca_signal_free(nfca_signal); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) { | void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) { | ||||||
|     ReturnCode err; |     ReturnCode err; | ||||||
|     uint8_t tx_buff[64] = {}; |     uint8_t tx_buff[64] = {}; | ||||||
|  | |||||||
| @ -19,6 +19,7 @@ typedef enum { | |||||||
|     NfcWorkerStateReadMifareUltralight, |     NfcWorkerStateReadMifareUltralight, | ||||||
|     NfcWorkerStateEmulateMifareUltralight, |     NfcWorkerStateEmulateMifareUltralight, | ||||||
|     NfcWorkerStateReadMifareClassic, |     NfcWorkerStateReadMifareClassic, | ||||||
|  |     NfcWorkerStateEmulateMifareClassic, | ||||||
|     NfcWorkerStateReadMifareDesfire, |     NfcWorkerStateReadMifareDesfire, | ||||||
|     // Transition
 |     // Transition
 | ||||||
|     NfcWorkerStateStop, |     NfcWorkerStateStop, | ||||||
|  | |||||||
| @ -34,4 +34,6 @@ ADD_SCENE(nfc, restore_original, RestoreOriginal) | |||||||
| ADD_SCENE(nfc, debug, Debug) | ADD_SCENE(nfc, debug, Debug) | ||||||
| ADD_SCENE(nfc, field, Field) | ADD_SCENE(nfc, field, Field) | ||||||
| ADD_SCENE(nfc, read_mifare_classic, ReadMifareClassic) | ADD_SCENE(nfc, read_mifare_classic, ReadMifareClassic) | ||||||
|  | ADD_SCENE(nfc, emulate_mifare_classic, EmulateMifareClassic) | ||||||
|  | ADD_SCENE(nfc, mifare_classic_menu, MifareClassicMenu) | ||||||
| ADD_SCENE(nfc, dict_not_found, DictNotFound) | ADD_SCENE(nfc, dict_not_found, DictNotFound) | ||||||
|  | |||||||
| @ -73,7 +73,7 @@ bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) { | |||||||
|         if(event.event == GuiButtonTypeLeft) { |         if(event.event == GuiButtonTypeLeft) { | ||||||
|             return scene_manager_previous_scene(nfc->scene_manager); |             return scene_manager_previous_scene(nfc->scene_manager); | ||||||
|         } else if(event.event == GuiButtonTypeRight) { |         } else if(event.event == GuiButtonTypeRight) { | ||||||
|             if(nfc_device_delete(nfc->dev)) { |             if(nfc_device_delete(nfc->dev, true)) { | ||||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess); |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess); | ||||||
|             } else { |             } else { | ||||||
|                 scene_manager_search_and_switch_to_previous_scene( |                 scene_manager_search_and_switch_to_previous_scene( | ||||||
|  | |||||||
							
								
								
									
										64
									
								
								applications/nfc/scenes/nfc_scene_emulate_mifare_classic.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								applications/nfc/scenes/nfc_scene_emulate_mifare_classic.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | |||||||
|  | #include "../nfc_i.h" | ||||||
|  | #include <dolphin/dolphin.h> | ||||||
|  | 
 | ||||||
|  | #define NFC_MF_CLASSIC_DATA_NOT_CHANGED (0UL) | ||||||
|  | #define NFC_MF_CLASSIC_DATA_CHANGED (1UL) | ||||||
|  | 
 | ||||||
|  | void nfc_emulate_mifare_classic_worker_callback(NfcWorkerEvent event, void* context) { | ||||||
|  |     UNUSED(event); | ||||||
|  |     Nfc* nfc = context; | ||||||
|  | 
 | ||||||
|  |     scene_manager_set_scene_state( | ||||||
|  |         nfc->scene_manager, NfcSceneEmulateMifareClassic, NFC_MF_CLASSIC_DATA_CHANGED); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_emulate_mifare_classic_on_enter(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     DOLPHIN_DEED(DolphinDeedNfcEmulate); | ||||||
|  | 
 | ||||||
|  |     // Setup view
 | ||||||
|  |     Popup* popup = nfc->popup; | ||||||
|  |     if(strcmp(nfc->dev->dev_name, "")) { | ||||||
|  |         nfc_text_store_set(nfc, "%s", nfc->dev->dev_name); | ||||||
|  |     } | ||||||
|  |     popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); | ||||||
|  |     popup_set_header(popup, "Emulating\nMf Classic", 56, 31, AlignLeft, AlignTop); | ||||||
|  | 
 | ||||||
|  |     // Setup and start worker
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); | ||||||
|  |     nfc_worker_start( | ||||||
|  |         nfc->worker, | ||||||
|  |         NfcWorkerStateEmulateMifareClassic, | ||||||
|  |         &nfc->dev->dev_data, | ||||||
|  |         nfc_emulate_mifare_classic_worker_callback, | ||||||
|  |         nfc); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_scene_emulate_mifare_classic_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeTick) { | ||||||
|  |         notification_message(nfc->notifications, &sequence_blink_blue_10); | ||||||
|  |         consumed = true; | ||||||
|  |     } else if(event.type == SceneManagerEventTypeBack) { | ||||||
|  |         // Stop worker
 | ||||||
|  |         nfc_worker_stop(nfc->worker); | ||||||
|  |         // Check if data changed and save in shadow file
 | ||||||
|  |         if(scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmulateMifareClassic) == | ||||||
|  |            NFC_MF_CLASSIC_DATA_CHANGED) { | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 nfc->scene_manager, NfcSceneEmulateMifareClassic, NFC_MF_CLASSIC_DATA_NOT_CHANGED); | ||||||
|  |             nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name); | ||||||
|  |         } | ||||||
|  |         consumed = false; | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_emulate_mifare_classic_on_exit(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  | 
 | ||||||
|  |     // Clear view
 | ||||||
|  |     popup_reset(nfc->popup); | ||||||
|  | } | ||||||
							
								
								
									
										64
									
								
								applications/nfc/scenes/nfc_scene_mifare_classic_menu.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								applications/nfc/scenes/nfc_scene_mifare_classic_menu.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | |||||||
|  | #include "../nfc_i.h" | ||||||
|  | 
 | ||||||
|  | enum SubmenuIndex { | ||||||
|  |     SubmenuIndexSave, | ||||||
|  |     SubmenuIndexEmulate, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mifare_classic_menu_submenu_callback(void* context, uint32_t index) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_send_custom_event(nfc->view_dispatcher, index); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mifare_classic_menu_on_enter(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     Submenu* submenu = nfc->submenu; | ||||||
|  | 
 | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, "Save", SubmenuIndexSave, nfc_scene_mifare_classic_menu_submenu_callback, nfc); | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Emulate", | ||||||
|  |         SubmenuIndexEmulate, | ||||||
|  |         nfc_scene_mifare_classic_menu_submenu_callback, | ||||||
|  |         nfc); | ||||||
|  |     submenu_set_selected_item( | ||||||
|  |         nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMifareUlMenu)); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_scene_mifare_classic_menu_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == SubmenuIndexSave) { | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 nfc->scene_manager, NfcSceneMifareUlMenu, SubmenuIndexSave); | ||||||
|  |             nfc->dev->format = NfcDeviceSaveFormatMifareClassic; | ||||||
|  |             // Clear device name
 | ||||||
|  |             nfc_device_set_name(nfc->dev, ""); | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == SubmenuIndexEmulate) { | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 nfc->scene_manager, NfcSceneMifareUlMenu, SubmenuIndexEmulate); | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareClassic); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } else if(event.type == SceneManagerEventTypeBack) { | ||||||
|  |         consumed = | ||||||
|  |             scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mifare_classic_menu_on_exit(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  | 
 | ||||||
|  |     // Clear view
 | ||||||
|  |     submenu_reset(nfc->submenu); | ||||||
|  | } | ||||||
| @ -47,7 +47,7 @@ bool nfc_scene_read_mifare_classic_on_event(void* context, SceneManagerEvent eve | |||||||
|         consumed = true; |         consumed = true; | ||||||
|     } else if(event.type == SceneManagerEventTypeCustom) { |     } else if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == NfcCustomEventDictAttackDone) { |         if(event.event == NfcCustomEventDictAttackDone) { | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneMifareClassicMenu); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == NfcWorkerEventDetectedClassic1k) { |         } else if(event.event == NfcWorkerEventDetectedClassic1k) { | ||||||
|             dict_attack_card_detected(nfc->dict_attack, MfClassicType1k); |             dict_attack_card_detected(nfc->dict_attack, MfClassicType1k); | ||||||
| @ -71,7 +71,6 @@ bool nfc_scene_read_mifare_classic_on_event(void* context, SceneManagerEvent eve | |||||||
|             scene_manager_set_scene_state( |             scene_manager_set_scene_state( | ||||||
|                 nfc->scene_manager, NfcSceneReadMifareClassic, NfcSceneReadMifareClassicStateDone); |                 nfc->scene_manager, NfcSceneReadMifareClassic, NfcSceneReadMifareClassicStateDone); | ||||||
|             notification_message(nfc->notifications, &sequence_success); |             notification_message(nfc->notifications, &sequence_success); | ||||||
|             nfc->dev->format = NfcDeviceSaveFormatMifareClassic; |  | ||||||
|             dict_attack_set_result(nfc->dict_attack, true); |             dict_attack_set_result(nfc->dict_attack, true); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == NfcWorkerEventFail) { |         } else if(event.event == NfcWorkerEventFail) { | ||||||
|  | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Aleksandr Kutuzov
						Aleksandr Kutuzov