Explorar el Código

".fap" extention in file browser and archive tab (#1812)

* Add .fap extention, and Applications tab
* Using new icon, renaming tab to Apps
* Change tabs order
* Add first ugly implementation of in-app icons in archive browser
* Starting using FAPLoader callback
* Getting all metafata from fap
* add app filename fallback
* using fap_loader_item_callback in archive_list_item_cb
* FAP-Loader: removed minimal allocation
* Removed strange code

Co-authored-by: SG <who.just.the.doctor@gmail.com>
Co-authored-by: あく <alleteam@gmail.com>
Max Andreev hace 3 años
padre
commit
d07c2dbe54

+ 20 - 3
applications/main/archive/helpers/archive_browser.c

@@ -5,6 +5,7 @@
 #include <core/common_defines.h>
 #include <core/log.h>
 #include "gui/modules/file_browser_worker.h"
+#include <fap_loader/fap_loader_app.h>
 #include <math.h>
 
 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);

+ 3 - 0
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,
 };
 

+ 32 - 6
applications/main/archive/helpers/archive_files.h

@@ -4,6 +4,8 @@
 #include <furi.h>
 #include <storage/storage.h>
 
+#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->path = furi_string_alloc();
     obj->type = ArchiveFileTypeUnknown;
-    obj->is_app = false;
+    obj->custom_icon_data = NULL;
+    obj->custom_name = furi_string_alloc();
     obj->fav = false;
-    obj->path = furi_string_alloc();
+    obj->is_app = false;
 }
 
 static void ArchiveFile_t_init_set(ArchiveFile_t* obj, const ArchiveFile_t* src) {
+    obj->path = furi_string_alloc_set(src->path);
     obj->type = src->type;
-    obj->is_app = src->is_app;
+    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->path = furi_string_alloc_set(src->path);
+    obj->is_app = src->is_app;
 }
 
 static void ArchiveFile_t_set(ArchiveFile_t* obj, const ArchiveFile_t* src) {
+    furi_string_set(obj->path, src->path);
     obj->type = src->type;
-    obj->is_app = src->is_app;
+    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;
-    furi_string_set(obj->path, src->path);
+    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(

+ 1 - 0
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) {

+ 21 - 2
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));
 

+ 1 - 0
applications/main/archive/views/archive_browser_view.h

@@ -26,6 +26,7 @@ typedef enum {
     ArchiveTabIButton,
     ArchiveTabBadUsb,
     ArchiveTabU2f,
+    ArchiveTabApplications,
     ArchiveTabBrowser,
     ArchiveTabTotal,
 } ArchiveTabEnum;

+ 40 - 28
applications/main/fap_loader/fap_loader_app.c

@@ -1,34 +1,31 @@
 #include <furi.h>
 #include <gui/gui.h>
 #include <gui/view_dispatcher.h>
-#include <gui/modules/loading.h>
 #include <storage/storage.h>
+#include <gui/modules/loading.h>
 #include <dialogs/dialogs.h>
-#include "elf_cpp/elf_hashtable.h"
 #include <flipper_application/flipper_application.h>
+#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;
-}
+}

+ 27 - 0
applications/main/fap_loader/fap_loader_app.h

@@ -0,0 +1,27 @@
+#pragma once
+#include <storage/storage.h>
+
+#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