diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 1f4ca0f7..f6603070 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -5,6 +5,7 @@ #include #include #include "gui/modules/file_browser_worker.h" +#include #include static void @@ -351,16 +352,32 @@ void archive_add_app_item(ArchiveBrowserView* browser, const char* name) { ArchiveFile_t_clear(&item); } +static bool archive_get_fap_meta(FuriString* file_path, FuriString* fap_name, uint8_t** icon_ptr) { + Storage* storage = furi_record_open(RECORD_STORAGE); + bool success = false; + if(fap_loader_load_name_and_icon(file_path, storage, icon_ptr, fap_name)) { + success = true; + } + furi_record_close(RECORD_STORAGE); + return success; +} + void archive_add_file_item(ArchiveBrowserView* browser, bool is_folder, const char* name) { furi_assert(browser); furi_assert(name); ArchiveFile_t item; - ArchiveFile_t_init(&item); - item.path = furi_string_alloc_set(name); - archive_set_file_type(&item, furi_string_get_cstr(browser->path), is_folder, false); + furi_string_set(item.path, name); + archive_set_file_type(&item, furi_string_get_cstr(browser->path), is_folder, false); + if(item.type == ArchiveFileTypeApplication) { + item.custom_icon_data = malloc(FAP_MANIFEST_MAX_ICON_SIZE); + if(!archive_get_fap_meta(item.path, item.custom_name, &item.custom_icon_data)) { + free(item.custom_icon_data); + item.custom_icon_data = NULL; + } + } with_view_model( browser->view, (ArchiveBrowserViewModel * model) { files_array_push_back(model->files, item); diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index c9e3bfa3..519a34a2 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -16,6 +16,7 @@ static const char* tab_default_paths[] = { [ArchiveTabInfrared] = ANY_PATH("infrared"), [ArchiveTabBadUsb] = ANY_PATH("badusb"), [ArchiveTabU2f] = "/app:u2f", + [ArchiveTabApplications] = ANY_PATH("apps"), [ArchiveTabBrowser] = STORAGE_ANY_PATH_PREFIX, }; @@ -27,6 +28,7 @@ static const char* known_ext[] = { [ArchiveFileTypeInfrared] = ".ir", [ArchiveFileTypeBadUsb] = ".txt", [ArchiveFileTypeU2f] = "?", + [ArchiveFileTypeApplication] = ".fap", [ArchiveFileTypeUpdateManifest] = ".fuf", [ArchiveFileTypeFolder] = "?", [ArchiveFileTypeUnknown] = "*", @@ -41,6 +43,7 @@ static const ArchiveFileTypeEnum known_type[] = { [ArchiveTabInfrared] = ArchiveFileTypeInfrared, [ArchiveTabBadUsb] = ArchiveFileTypeBadUsb, [ArchiveTabU2f] = ArchiveFileTypeU2f, + [ArchiveTabApplications] = ArchiveFileTypeApplication, [ArchiveTabBrowser] = ArchiveFileTypeUnknown, }; diff --git a/applications/main/archive/helpers/archive_files.h b/applications/main/archive/helpers/archive_files.h index a50a1d53..2017a957 100644 --- a/applications/main/archive/helpers/archive_files.h +++ b/applications/main/archive/helpers/archive_files.h @@ -4,6 +4,8 @@ #include #include +#define FAP_MANIFEST_MAX_ICON_SIZE 32 + typedef enum { ArchiveFileTypeIButton, ArchiveFileTypeNFC, @@ -13,6 +15,7 @@ typedef enum { ArchiveFileTypeBadUsb, ArchiveFileTypeU2f, ArchiveFileTypeUpdateManifest, + ArchiveFileTypeApplication, ArchiveFileTypeFolder, ArchiveFileTypeUnknown, ArchiveFileTypeLoading, @@ -21,33 +24,56 @@ typedef enum { typedef struct { FuriString* path; ArchiveFileTypeEnum type; + uint8_t* custom_icon_data; + FuriString* custom_name; bool fav; bool is_app; } ArchiveFile_t; static void ArchiveFile_t_init(ArchiveFile_t* obj) { - obj->type = ArchiveFileTypeUnknown; - obj->is_app = false; - obj->fav = false; obj->path = furi_string_alloc(); + obj->type = ArchiveFileTypeUnknown; + obj->custom_icon_data = NULL; + obj->custom_name = furi_string_alloc(); + obj->fav = false; + obj->is_app = false; } static void ArchiveFile_t_init_set(ArchiveFile_t* obj, const ArchiveFile_t* src) { - obj->type = src->type; - obj->is_app = src->is_app; - obj->fav = src->fav; obj->path = furi_string_alloc_set(src->path); + obj->type = src->type; + if(src->custom_icon_data) { + obj->custom_icon_data = malloc(FAP_MANIFEST_MAX_ICON_SIZE); + memcpy(obj->custom_icon_data, src->custom_icon_data, FAP_MANIFEST_MAX_ICON_SIZE); + } else { + obj->custom_icon_data = NULL; + } + obj->custom_name = furi_string_alloc_set(src->custom_name); + obj->fav = src->fav; + obj->is_app = src->is_app; } static void ArchiveFile_t_set(ArchiveFile_t* obj, const ArchiveFile_t* src) { - obj->type = src->type; - obj->is_app = src->is_app; - obj->fav = src->fav; furi_string_set(obj->path, src->path); + obj->type = src->type; + if(src->custom_icon_data) { + obj->custom_icon_data = malloc(FAP_MANIFEST_MAX_ICON_SIZE); + memcpy(obj->custom_icon_data, src->custom_icon_data, FAP_MANIFEST_MAX_ICON_SIZE); + } else { + obj->custom_icon_data = NULL; + } + furi_string_set(obj->custom_name, src->custom_name); + obj->fav = src->fav; + obj->is_app = src->is_app; } static void ArchiveFile_t_clear(ArchiveFile_t* obj) { furi_string_free(obj->path); + if(obj->custom_icon_data) { + free(obj->custom_icon_data); + obj->custom_icon_data = NULL; + } + furi_string_free(obj->custom_name); } ARRAY_DEF( diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index b4fd0482..9dc67161 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -20,6 +20,7 @@ static const char* flipper_app_name[] = { [ArchiveFileTypeBadUsb] = "Bad USB", [ArchiveFileTypeU2f] = "U2F", [ArchiveFileTypeUpdateManifest] = "UpdaterApp", + [ArchiveFileTypeApplication] = "Applications", }; static void archive_loader_callback(const void* message, void* context) { diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 4a0f2f3c..f13094a1 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -14,6 +14,7 @@ static const char* ArchiveTabNames[] = { [ArchiveTabInfrared] = "Infrared", [ArchiveTabBadUsb] = "Bad USB", [ArchiveTabU2f] = "U2F", + [ArchiveTabApplications] = "Apps", [ArchiveTabBrowser] = "Browser", }; @@ -29,6 +30,7 @@ static const Icon* ArchiveItemIcons[] = { [ArchiveFileTypeFolder] = &I_dir_10px, [ArchiveFileTypeUnknown] = &I_unknown_10px, [ArchiveFileTypeLoading] = &I_loading_10px, + [ArchiveFileTypeApplication] = &I_unknown_10px, }; void archive_browser_set_callback( @@ -124,12 +126,23 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { uint8_t x_offset = (model->move_fav && model->item_idx == idx) ? MOVE_OFFSET : 0; ArchiveFileTypeEnum file_type = ArchiveFileTypeLoading; + uint8_t* custom_icon_data = NULL; if(archive_is_item_in_array(model, idx)) { ArchiveFile_t* file = files_array_get( model->files, CLAMP(idx - model->array_offset, (int32_t)(array_size - 1), 0)); - path_extract_filename(file->path, str_buf, archive_is_known_app(file->type)); file_type = file->type; + if(file_type == ArchiveFileTypeApplication) { + if(file->custom_icon_data) { + custom_icon_data = file->custom_icon_data; + furi_string_set(str_buf, file->custom_name); + } else { + file_type = ArchiveFileTypeUnknown; + path_extract_filename(file->path, str_buf, archive_is_known_app(file->type)); + } + } else { + path_extract_filename(file->path, str_buf, archive_is_known_app(file->type)); + } } else { furi_string_set(str_buf, "---"); } @@ -143,7 +156,13 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { canvas_set_color(canvas, ColorBlack); } - canvas_draw_icon(canvas, 2 + x_offset, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file_type]); + if(custom_icon_data) { + canvas_draw_bitmap( + canvas, 2 + x_offset, 16 + i * FRAME_HEIGHT, 11, 10, custom_icon_data); + } else { + canvas_draw_icon( + canvas, 2 + x_offset, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file_type]); + } canvas_draw_str( canvas, 15 + x_offset, 24 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buf)); diff --git a/applications/main/archive/views/archive_browser_view.h b/applications/main/archive/views/archive_browser_view.h index 447cb0e1..308af4e4 100644 --- a/applications/main/archive/views/archive_browser_view.h +++ b/applications/main/archive/views/archive_browser_view.h @@ -26,6 +26,7 @@ typedef enum { ArchiveTabIButton, ArchiveTabBadUsb, ArchiveTabU2f, + ArchiveTabApplications, ArchiveTabBrowser, ArchiveTabTotal, } ArchiveTabEnum; diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index 1486cc1a..fb0a300e 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -1,34 +1,31 @@ #include #include #include -#include #include +#include #include -#include "elf_cpp/elf_hashtable.h" #include +#include "elf_cpp/elf_hashtable.h" +#include "fap_loader_app.h" #define TAG "fap_loader_app" -typedef struct { +struct FapLoader { FlipperApplication* app; Storage* storage; DialogsApp* dialogs; Gui* gui; FuriString* fap_path; - ViewDispatcher* view_dispatcher; Loading* loading; -} FapLoader; +}; -static bool fap_loader_item_callback( +bool fap_loader_load_name_and_icon( FuriString* path, - void* context, + Storage* storage, uint8_t** icon_ptr, FuriString* item_name) { - FapLoader* loader = context; - furi_assert(loader); - - FlipperApplication* app = flipper_application_alloc(loader->storage, &hashtable_api_interface); + FlipperApplication* app = flipper_application_alloc(storage, &hashtable_api_interface); FlipperApplicationPreloadStatus preload_res = flipper_application_preload_manifest(app, furi_string_get_cstr(path)); @@ -51,6 +48,16 @@ static bool fap_loader_item_callback( return load_success; } +static bool fap_loader_item_callback( + FuriString* path, + void* context, + uint8_t** icon_ptr, + FuriString* item_name) { + FapLoader* fap_loader = context; + furi_assert(fap_loader); + return fap_loader_load_name_and_icon(path, fap_loader->storage, icon_ptr, item_name); +} + static bool fap_loader_run_selected_app(FapLoader* loader) { furi_assert(loader); @@ -134,7 +141,7 @@ static bool fap_loader_select_app(FapLoader* loader) { const DialogsFileBrowserOptions browser_options = { .extension = ".fap", .skip_assets = true, - .icon = &I_badusb_10px, + .icon = &I_unknown_10px, .hide_ext = true, .item_loader_callback = fap_loader_item_callback, .item_loader_context = loader, @@ -144,39 +151,44 @@ static bool fap_loader_select_app(FapLoader* loader) { loader->dialogs, loader->fap_path, loader->fap_path, &browser_options); } -int32_t fap_loader_app(void* p) { +static FapLoader* fap_loader_alloc(const char* path) { FapLoader* loader = malloc(sizeof(FapLoader)); + loader->fap_path = furi_string_alloc_set(path); loader->storage = furi_record_open(RECORD_STORAGE); loader->dialogs = furi_record_open(RECORD_DIALOGS); loader->gui = furi_record_open(RECORD_GUI); - loader->view_dispatcher = view_dispatcher_alloc(); loader->loading = loading_alloc(); - view_dispatcher_attach_to_gui( loader->view_dispatcher, loader->gui, ViewDispatcherTypeFullscreen); view_dispatcher_add_view(loader->view_dispatcher, 0, loading_get_view(loader->loading)); + return loader; +} +static void fap_loader_free(FapLoader* loader) { + view_dispatcher_remove_view(loader->view_dispatcher, 0); + loading_free(loader->loading); + view_dispatcher_free(loader->view_dispatcher); + furi_string_free(loader->fap_path); + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_STORAGE); + free(loader); +} + +int32_t fap_loader_app(void* p) { + FapLoader* loader; if(p) { - loader->fap_path = furi_string_alloc_set((const char*)p); + loader = fap_loader_alloc((const char*)p); fap_loader_run_selected_app(loader); } else { - loader->fap_path = furi_string_alloc_set(EXT_PATH("apps")); - + loader = fap_loader_alloc(EXT_PATH("apps")); while(fap_loader_select_app(loader)) { view_dispatcher_switch_to_view(loader->view_dispatcher, 0); fap_loader_run_selected_app(loader); }; } - view_dispatcher_remove_view(loader->view_dispatcher, 0); - loading_free(loader->loading); - view_dispatcher_free(loader->view_dispatcher); - - furi_string_free(loader->fap_path); - furi_record_close(RECORD_GUI); - furi_record_close(RECORD_DIALOGS); - furi_record_close(RECORD_STORAGE); - free(loader); + fap_loader_free(loader); return 0; -} \ No newline at end of file +} diff --git a/applications/main/fap_loader/fap_loader_app.h b/applications/main/fap_loader/fap_loader_app.h new file mode 100644 index 00000000..9ed725ef --- /dev/null +++ b/applications/main/fap_loader/fap_loader_app.h @@ -0,0 +1,27 @@ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FapLoader FapLoader; + +/** + * @brief Load name and icon from FAP file. + * + * @param path Path to FAP file. + * @param storage Storage instance. + * @param icon_ptr Icon pointer. + * @param item_name Application name. + * @return true if icon and name were loaded successfully. + */ +bool fap_loader_load_name_and_icon( + FuriString* path, + Storage* storage, + uint8_t** icon_ptr, + FuriString* item_name); + +#ifdef __cplusplus +} +#endif \ No newline at end of file