From 0abcb1172a13d0393032d4323153a892e2b0c439 Mon Sep 17 00:00:00 2001 From: yellows8 Date: Wed, 10 Oct 2018 18:55:37 -0400 Subject: [PATCH] Implemented support for file-associations, closes #25. Various improvements. Cleanup icon/icon_size state in menuEntryParseIcon() on failure. Added fsobjExists() which is now called at the start of menuEntryLoad(). Added menuEntryLoadExternalIcon() and menuEntryImportIconGfx(). --- common/menu-entry.c | 322 ++++++++++++++++++++++++++++++++++++++++++-- common/menu-list.c | 81 +++++++++-- common/menu.c | 12 +- common/menu.h | 16 ++- 4 files changed, 406 insertions(+), 25 deletions(-) diff --git a/common/menu-entry.c b/common/menu-entry.c index ca6de5a..41d447d 100644 --- a/common/menu-entry.c +++ b/common/menu-entry.c @@ -5,21 +5,23 @@ void menuEntryInit(menuEntry_s* me, MenuEntryType type) { me->type = type; } -void menuEntryFree(menuEntry_s* me) { +void menuEntryFree(menuEntry_s* me, bool skip_icongfx) { me->icon_size = 0; if (me->icon) { free(me->icon); me->icon = NULL; } - if (me->icon_gfx) { - free(me->icon_gfx); - me->icon_gfx = NULL; - } + if (!skip_icongfx) { + if (me->icon_gfx) { + free(me->icon_gfx); + me->icon_gfx = NULL; + } - if (me->icon_gfx_small) { - free(me->icon_gfx_small); - me->icon_gfx_small = NULL; + if (me->icon_gfx_small) { + free(me->icon_gfx_small); + me->icon_gfx_small = NULL; + } } if (me->nacp) { @@ -33,6 +35,11 @@ bool fileExists(const char* path) { return stat(path, &st)==0 && S_ISREG(st.st_mode); } +bool fsobjExists(const char* path) { + struct stat st; + return stat(path, &st)==0; +} + static bool menuEntryLoadEmbeddedIcon(menuEntry_s* me) { NroHeader header; NroAssetHeader asset_header; @@ -73,6 +80,50 @@ static bool menuEntryLoadEmbeddedIcon(menuEntry_s* me) { return ok; } +static bool menuEntryLoadExternalIcon(menuEntry_s* me, const char* path) { + struct stat st; + + if(stat(path, &st)==-1) return false; + + FILE* f = fopen(path, "rb"); + if (!f) return false; + + me->icon_size = st.st_size; + me->icon = (uint8_t*)malloc(me->icon_size); + if (me->icon == NULL) { + fclose(f); + return false; + } + memset(me->icon, 0, me->icon_size); + + bool ok = fread(me->icon, me->icon_size, 1, f) == 1; + fclose(f); + return ok; +} + +static bool menuEntryImportIconGfx(menuEntry_s* me, uint8_t* icon_gfx, uint8_t* icon_gfx_small) { + size_t tmpsize; + + if (icon_gfx == NULL || icon_gfx_small == NULL) return false; + + tmpsize = 256*256*3; + me->icon_gfx = (uint8_t*)malloc(tmpsize); + if (me->icon_gfx) memcpy(me->icon_gfx, icon_gfx, tmpsize); + + if (me->icon_gfx) { + tmpsize = 140*140*3; + me->icon_gfx_small = (uint8_t*)malloc(tmpsize); + if (me->icon_gfx_small) memcpy(me->icon_gfx_small, icon_gfx_small, tmpsize); + + if (me->icon_gfx_small == NULL) { + free(me->icon_gfx); + me->icon_gfx = NULL; + } + } + + return me->icon_gfx && me->icon_gfx_small; +} + static bool menuEntryLoadEmbeddedNacp(menuEntry_s* me) { NroHeader header; NroAssetHeader asset_header; @@ -132,9 +183,15 @@ static bool menuEntryLoadEmbeddedNacp(menuEntry_s* me) { }*/ bool menuEntryLoad(menuEntry_s* me, const char* name, bool shortcut) { + int i=0; + menu_s *menu_fileassoc = menuFileassocGetCurrent(); + menuEntry_s* fileassoc_me = NULL; + char *strptr = NULL; static char tempbuf[PATH_MAX+1]; //bool isOldAppFolder = false; + if (!fsobjExists(me->path)) return false; + tempbuf[PATH_MAX] = 0; strcpy(me->name, name); @@ -143,9 +200,10 @@ bool menuEntryLoad(menuEntry_s* me, const char* name, bool shortcut) { //Check for /.nro snprintf(tempbuf, sizeof(tempbuf)-1, "%.*s/%.*s.nro", (int)sizeof(tempbuf)/2, me->path, (int)sizeof(tempbuf)/2-7, name); bool found = fileExists(tempbuf); + bool fileassoc_flag = 0; //Use the first .nro found in the directory, if there's only 1 NRO in the directory. Only used for paths starting with "sdmc:/switch/". - if (!found && strncmp(me->path, "sdmc:/switch/", 13)==0) { + if (!found && strncmp(me->path, menuGetRootPath(), strlen(menuGetRootPath()))==0) { DIR* dir; struct dirent* dp; u32 nro_count=0; @@ -170,13 +228,36 @@ bool menuEntryLoad(menuEntry_s* me, const char* name, bool shortcut) { } closedir(dir); } + + if (!found && menu_fileassoc->nEntries > 0) { + fileassoc_flag = 1; + dir = opendir(me->path); + if (dir) { + while ((dp = readdir(dir))) { + if (dp->d_name[0]=='.')//Check this here so that it's consistent with menuScan(). + continue; + + for (fileassoc_me = menu_fileassoc->firstEntry, i = 0; fileassoc_me; fileassoc_me = fileassoc_me->next, i++) { + if (!fileassoc_me->fileassoc_type) continue; //Only handle fileassoc entries for filenames, not file_extensions. + + if (strcmp(dp->d_name, fileassoc_me->fileassoc_str)) continue; + + snprintf(tempbuf, sizeof(tempbuf)-1, "%.*s/%.*s", (int)sizeof(tempbuf)/2, me->path, (int)sizeof(tempbuf)/2-7, dp->d_name); + found = fileExists(tempbuf); + if (found) break; + } + if (found) break; + } + closedir(dir); + } + } } if (found) { //isOldAppFolder = true; shortcut = false; - me->type = ENTRY_TYPE_FILE; + me->type = fileassoc_flag ? ENTRY_TYPE_FILE_OTHER : ENTRY_TYPE_FILE; strcpy(me->path, tempbuf); } } @@ -318,9 +399,206 @@ bool menuEntryLoad(menuEntry_s* me, const char* name, bool shortcut) { config_destroy(&cfg); } + if (me->type == ENTRY_TYPE_FILE_OTHER) + { + if (menu_fileassoc->nEntries == 0) return false; + + for (fileassoc_me = menu_fileassoc->firstEntry, i = 0; fileassoc_me; fileassoc_me = fileassoc_me->next, i++) { + //For fileassoc_type==0 compare the extension, otherwise compare the filename. + if (!fileassoc_me->fileassoc_type) { + strptr = getExtension(me->path); + } + if (fileassoc_me->fileassoc_type) { + strptr = getSlash(me->path); + if (strptr[0] == '/') strptr++; + } + + if (strcmp(strptr, fileassoc_me->fileassoc_str)) continue; + + //At this point a match was found. + + me->type = ENTRY_TYPE_FILE; + + //Attempt to load the icon from {me->path filepath with extension .jpg}, then on failure use the icon data from fileassoc_me. + memset(tempbuf, 0, sizeof(tempbuf)); + strncpy(tempbuf, me->path, sizeof(tempbuf)); + tempbuf[sizeof(tempbuf)-1] = 0; + strptr = getExtension(tempbuf); + strncpy(strptr, ".jpg", sizeof(tempbuf)-1 - ((ptrdiff_t)strptr - (ptrdiff_t)tempbuf)); + + bool iconLoaded = false; + + iconLoaded = menuEntryLoadExternalIcon(me, tempbuf); + + if (iconLoaded) menuEntryParseIcon(me); + + if (iconLoaded && !(me->icon_gfx && me->icon_gfx_small)) iconLoaded = 0; + + if (!iconLoaded && fileassoc_me->icon_gfx && fileassoc_me->icon_gfx_small) + iconLoaded = menuEntryImportIconGfx(me, fileassoc_me->icon_gfx, fileassoc_me->icon_gfx_small); + + strncpy(me->author, fileassoc_me->author, sizeof(me->author)); + me->author[sizeof(me->author)-1] = 0; + + strncpy(me->version, fileassoc_me->version, sizeof(me->version)); + me->version[sizeof(me->version)-1] = 0; + + // Initialize the argument data + argData_s* ad = &me->args; + ad->dst = (char*)&ad->buf[1]; + launchAddArg(ad, fileassoc_me->path); + launchAddArg(ad, me->path); + + strncpy(me->path, fileassoc_me->path, sizeof(me->path)); + me->path[sizeof(me->path)-1] = 0; + + return true; + } + return false; + } + return true; } +void menuEntryFileassocLoad(const char* filepath) { + bool success=0, success2=0; + menuEntry_s* me = NULL; + + config_setting_t *fileassoc = NULL, *targets = NULL, *target = NULL; + config_t cfg = {0}; + int targets_len=0, i; + const char *strptr = NULL; + + char app_path[PATH_MAX+8]; + char main_icon_path[PATH_MAX+1]; + char target_icon_path[PATH_MAX+1]; + char target_file_extension[PATH_MAX+1]; + char target_filename[PATH_MAX+1]; + + char app_author[ENTRY_AUTHORLENGTH+1]; + char app_version[ENTRY_VERLENGTH+1]; + + uint8_t *app_icon_gfx = NULL; + uint8_t *app_icon_gfx_small = NULL; + + config_init(&cfg); + + memset(app_path, 0, sizeof(app_path)); + memset(main_icon_path, 0, sizeof(main_icon_path)); + memset(app_author, 0, sizeof(app_author)); + memset(app_version, 0, sizeof(app_version)); + + if (!fileExists(filepath)) return; + + if (config_read_file(&cfg, filepath)) { + fileassoc = config_lookup(&cfg, "fileassoc"); + + if (fileassoc != NULL) { + if (config_setting_lookup_string(fileassoc, "app_path", &strptr)) + snprintf(app_path, sizeof(app_path)-1, "%s%s", menuGetRootBasePath(), strptr); + if (config_setting_lookup_string(fileassoc, "icon_path", &strptr)) + snprintf(main_icon_path, sizeof(main_icon_path)-1, "%s%s", menuGetRootBasePath(), strptr); + targets = config_setting_get_member(fileassoc, "targets"); + + if (app_path[0] && targets) { + targets_len = config_setting_length(targets); + + if (targets_len > 0) { + //Load the author/version and icon data with the NRO app path. + me = menuCreateEntry(ENTRY_TYPE_FILE); + success = 0; + if (me) { + strncpy(me->path, app_path, sizeof(me->path)-1); + me->path[sizeof(me->path)-1] = 0; + + strptr = getSlash(app_path); + if(strptr[0] == '/') strptr++; + + if (menuEntryLoad(me, strptr, 0)) { + strncpy(app_author, me->author, sizeof(app_author)); + app_author[sizeof(app_author)-1] = 0; + strncpy(app_version, me->version, sizeof(app_version)); + app_version[sizeof(app_version)-1] = 0; + app_icon_gfx = me->icon_gfx; + app_icon_gfx_small = me->icon_gfx_small; + success = 1; + } + + menuDeleteEntry(me, success); + me = NULL; + } + + //Process the targets list. + if (success) { + for (i=0; ipath, app_path, sizeof(me->path)); + me->path[sizeof(me->path)-1] = 0; + strncpy(me->author, app_author, sizeof(me->author)); + me->author[sizeof(me->author)-1] = 0; + strncpy(me->version, app_version, sizeof(me->version)); + me->version[sizeof(me->version)-1] = 0; + + if (target_file_extension[0]) { + me->fileassoc_type = 0; + strncpy(me->fileassoc_str, target_file_extension, sizeof(me->fileassoc_str)); + } else if (target_filename[0]) { + me->fileassoc_type = 1; + strncpy(me->fileassoc_str, target_filename, sizeof(me->fileassoc_str)); + } + me->fileassoc_str[sizeof(me->fileassoc_str)-1] = 0; + + if (target_icon_path[0]) success2 = menuEntryLoadExternalIcon(me, target_icon_path); + if (!success2 && main_icon_path[0]) success2 = menuEntryLoadExternalIcon(me, main_icon_path); + + if (success2) { + menuEntryParseIcon(me); + } else { + success2 = menuEntryImportIconGfx(me, app_icon_gfx, app_icon_gfx_small); + } + } + + if (me) { + if (success2) + menuFileassocAddEntry(me); + else + menuDeleteEntry(me, 0); + } + } + } + } + } + } + } + + if (success) { + free(app_icon_gfx); + free(app_icon_gfx_small); + } + + config_destroy(&cfg); +} + void menuEntryParseIcon(menuEntry_s* me) { if (me->icon_size==0 || me->icon==NULL) return; @@ -328,19 +606,32 @@ void menuEntryParseIcon(menuEntry_s* me) { size_t imagesize = 256*256*3; me->icon_gfx = (uint8_t*)malloc(imagesize); - if (me->icon_gfx == NULL) return; + if (me->icon_gfx == NULL) { + me->icon_size = 0; + free(me->icon); + me->icon = NULL; + return; + } tjhandle _jpegDecompressor = tjInitDecompress(); if (_jpegDecompressor == NULL) { free(me->icon_gfx); me->icon_gfx = NULL; + + me->icon_size = 0; + free(me->icon); + me->icon = NULL; return; } if (tjDecompressHeader2(_jpegDecompressor, me->icon, me->icon_size, &w, &h, &samp) == -1) { free(me->icon_gfx); me->icon_gfx = NULL; + + me->icon_size = 0; + free(me->icon); + me->icon = NULL; tjDestroy(_jpegDecompressor); return; } @@ -350,6 +641,10 @@ void menuEntryParseIcon(menuEntry_s* me) { if (tjDecompress2(_jpegDecompressor, me->icon, me->icon_size, me->icon_gfx, w, 0, h, TJPF_RGB, TJFLAG_ACCURATEDCT) == -1) { free(me->icon_gfx); me->icon_gfx = NULL; + + me->icon_size = 0; + free(me->icon); + me->icon = NULL; tjDestroy(_jpegDecompressor); return; } @@ -361,6 +656,11 @@ void menuEntryParseIcon(menuEntry_s* me) { tjDestroy(_jpegDecompressor); me->icon_gfx_small = downscaleImg(me->icon_gfx, 256, 256, 140, 140, IMAGE_MODE_RGB24); + + if (me->icon_gfx_small == NULL) { + free(me->icon_gfx); + me->icon_gfx = NULL; + } } uint8_t *downscaleImg(const uint8_t *image, int srcWidth, int srcHeight, int destWidth, int destHeight, ImageMode mode) { diff --git a/common/menu-list.c b/common/menu-list.c index 5f35038..4341247 100644 --- a/common/menu-list.c +++ b/common/menu-list.c @@ -1,25 +1,29 @@ #include "common.h" static menu_s s_menu[2]; -static bool s_curMenu; +static menu_s s_menuFileassoc[2]; +static bool s_curMenu, s_curMenuFileassoc; menu_s* menuGetCurrent(void) { return &s_menu[s_curMenu]; } -static menuEntry_s* menuCreateEntry(MenuEntryType type) { +menu_s* menuFileassocGetCurrent(void) { + return &s_menuFileassoc[s_curMenuFileassoc]; +} + +menuEntry_s* menuCreateEntry(MenuEntryType type) { menuEntry_s* me = (menuEntry_s*)malloc(sizeof(menuEntry_s)); menuEntryInit(me, type); return me; } -static void menuDeleteEntry(menuEntry_s* me) { - menuEntryFree(me); +void menuDeleteEntry(menuEntry_s* me, bool skip_icongfx) { + menuEntryFree(me, skip_icongfx); free(me); } -static void menuAddEntry(menuEntry_s* me) { - menu_s* m = &s_menu[!s_curMenu]; +static void _menuAddEntry(menu_s *m, menuEntry_s* me) { me->menu = m; if (m->lastEntry) { @@ -34,6 +38,14 @@ static void menuAddEntry(menuEntry_s* me) { m->nEntries ++; } +static void menuAddEntry(menuEntry_s* me) { + _menuAddEntry(&s_menu[!s_curMenu], me); +} + +void menuFileassocAddEntry(menuEntry_s* me) { + _menuAddEntry(&s_menuFileassoc[!s_curMenuFileassoc], me); +} + static void menuAddEntryToFront(menuEntry_s* me) { menu_s* m = &s_menu[!s_curMenu]; me->menu = m; @@ -50,17 +62,24 @@ static void menuAddEntryToFront(menuEntry_s* me) { m->nEntries ++; } -static void menuClear(void) { - menu_s* m = &s_menu[!s_curMenu]; +static void _menuClear(menu_s* m) { menuEntry_s *cur, *next; for (cur = m->firstEntry; cur; cur = next) { next = cur->next; - menuDeleteEntry(cur); + menuDeleteEntry(cur, 0); } memset(m, 0, sizeof(*m)); } +static void menuClear(void) { + _menuClear(&s_menu[!s_curMenu]); +} + +static void menuFileassocClear(void) { + _menuClear(&s_menuFileassoc[!s_curMenuFileassoc]); +} + static int menuEntryCmp(const void *p1, const void *p2) { const menuEntry_s* lhs = *(menuEntry_s**)p1; const menuEntry_s* rhs = *(menuEntry_s**)p2; @@ -144,6 +163,9 @@ int menuScan(const char* target) { const char* ext = getExtension(dp->d_name); if (strcasecmp(ext, ".nro")==0/* || (shortcut = strcasecmp(ext, ".xml")==0)*/) me = menuCreateEntry(ENTRY_TYPE_FILE); + + if (!me) + me = menuCreateEntry(ENTRY_TYPE_FILE_OTHER); } if (!me) @@ -151,10 +173,11 @@ int menuScan(const char* target) { strncpy(me->path, tmp_path, sizeof(me->path)-1); me->path[sizeof(me->path)-1] = 0; + if (menuEntryLoad(me, dp->d_name, shortcut)) menuAddEntry(me); else - menuDeleteEntry(me); + menuDeleteEntry(me, 0); } closedir(dir); @@ -200,7 +223,7 @@ int themeMenuScan(const char* target) { if (menuEntryLoad(me, dp->d_name, shortcut)) menuAddEntry(me); else - menuDeleteEntry(me); + menuDeleteEntry(me, 0); } closedir(dir); @@ -212,10 +235,44 @@ int themeMenuScan(const char* target) { if(menuEntryLoad(me, textGetString(StrId_DefaultThemeName), false))//Create Default theme Menu Entry menuAddEntryToFront(me); else - menuDeleteEntry(me); + menuDeleteEntry(me, 0); } // Swap the menu and clear the previous menu s_curMenu = !s_curMenu; menuClear(); return 0; } + +int menuFileassocScan(const char* target) { + menuFileassocClear(); + if (chdir(target) < 0) return 1; + if (getcwd(s_menuFileassoc[!s_curMenuFileassoc].dirname, PATH_MAX+1) == NULL) + return 1; + DIR* dir; + struct dirent* dp; + char tmp_path[PATH_MAX+1]; + dir = opendir(s_menuFileassoc[!s_curMenuFileassoc].dirname); + if (!dir) return 2; + + while ((dp = readdir(dir))) + { + if (dp->d_name[0]=='.') + continue; + + memset(tmp_path, 0, sizeof(tmp_path)); + snprintf(tmp_path, sizeof(tmp_path)-1, "%s/%s", s_menuFileassoc[!s_curMenuFileassoc].dirname, dp->d_name); + + const char* ext = getExtension(dp->d_name); + if (strcasecmp(ext, ".cfg")!=0) + continue; + + menuEntryFileassocLoad(tmp_path); + } + + closedir(dir); + + // Swap the menu and clear the previous menu + s_curMenuFileassoc = !s_curMenuFileassoc; + menuFileassocClear(); + return 0; +} diff --git a/common/menu.c b/common/menu.c index 307b18c..ccb18f3 100644 --- a/common/menu.c +++ b/common/menu.c @@ -309,7 +309,7 @@ void computeFrontGradient(color_t baseColor, int height) { } void menuStartupPath(void) { - char tmp_path[PATH_MAX+25]; + char tmp_path[PATH_MAX+28]; #ifdef __SWITCH__ strncpy(rootPathBase, "sdmc:", sizeof(rootPathBase)-1); @@ -335,9 +335,19 @@ void menuStartupPath(void) { snprintf(tmp_path, sizeof(tmp_path)-1, "%s/config/nx-hbmenu/themes", rootPathBase); mkdir(tmp_path, 0755); } + + snprintf(tmp_path, sizeof(tmp_path)-1, "%s/config/nx-hbmenu/fileassoc", rootPathBase); + if (stat(tmp_path, &st) == -1) { + mkdir(tmp_path, 0755); + } } void menuStartup(void) { + char tmp_path[PATH_MAX+28]; + + snprintf(tmp_path, sizeof(tmp_path)-1, "%s/config/nx-hbmenu/fileassoc", rootPathBase); + menuFileassocScan(tmp_path); + menuScan(rootPath); folder_icon_small = downscaleImg(folder_icon_bin, 256, 256, 140, 140, IMAGE_MODE_RGB24); diff --git a/common/menu.h b/common/menu.h index f7fac55..44ff1dd 100644 --- a/common/menu.h +++ b/common/menu.h @@ -20,6 +20,8 @@ typedef enum ENTRY_TYPE_FILE, ENTRY_TYPE_FOLDER, ENTRY_TYPE_THEME, + ENTRY_TYPE_FILEASSOC, + ENTRY_TYPE_FILE_OTHER, } MenuEntryType; typedef struct menuEntry_s_tag menuEntry_s; @@ -51,6 +53,9 @@ struct menuEntry_s_tag char path[PATH_MAX+8]; argData_s args; + bool fileassoc_type;//0=file_extension, 1 = filename + char fileassoc_str[PATH_MAX+1];//file_extension/filename + char name[ENTRY_NAMELENGTH+1]; char author[ENTRY_AUTHORLENGTH+1]; char version[ENTRY_VERLENGTH+1]; @@ -76,16 +81,25 @@ extern "C" { #endif void menuEntryInit(menuEntry_s* me, MenuEntryType type); -void menuEntryFree(menuEntry_s* me); +void menuEntryFree(menuEntry_s* me, bool skip_icongfx); bool fileExists(const char* path); bool menuEntryLoad(menuEntry_s* me, const char* name, bool shortcut); void menuEntryParseIcon(menuEntry_s* me); uint8_t *downscaleImg(const uint8_t *image, int srcWidth, int srcHeight, int destWidth, int destHeight, ImageMode mode); void menuEntryParseNacp(menuEntry_s* me); +void menuEntryFileassocLoad(const char* filepath); + +menuEntry_s* menuCreateEntry(MenuEntryType type); + +void menuFileassocAddEntry(menuEntry_s* me); +void menuDeleteEntry(menuEntry_s* me, bool skip_icongfx); + menu_s* menuGetCurrent(void); +menu_s* menuFileassocGetCurrent(void); int menuScan(const char* target); int themeMenuScan(const char* target); +int menuFileassocScan(const char* target); void launchMenuEntryTask(menuEntry_s* arg); void launchApplyThemeTask(menuEntry_s* arg);