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().

This commit is contained in:
yellows8 2018-10-10 18:55:37 -04:00
parent 40e971ba99
commit 0abcb1172a
4 changed files with 406 additions and 25 deletions

View File

@ -5,13 +5,14 @@ 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 (!skip_icongfx) {
if (me->icon_gfx) {
free(me->icon_gfx);
me->icon_gfx = NULL;
@ -21,6 +22,7 @@ void menuEntryFree(menuEntry_s* me) {
free(me->icon_gfx_small);
me->icon_gfx_small = NULL;
}
}
if (me->nacp) {
free(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 <dirpath>/<dirname>.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,8 +399,205 @@ 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; i<targets_len; i++) {
target = config_setting_get_elem(targets, i);
if (target == NULL) continue;
memset(target_icon_path, 0, sizeof(target_icon_path));
memset(target_file_extension, 0, sizeof(target_file_extension));
memset(target_filename, 0, sizeof(target_filename));
if (config_setting_lookup_string(target, "icon_path", &strptr))
snprintf(target_icon_path, sizeof(target_icon_path)-1, "%s%s", menuGetRootBasePath(), strptr);
if (config_setting_lookup_string(target, "file_extension", &strptr))
strncpy(target_file_extension, strptr, sizeof(target_file_extension)-1);
if (config_setting_lookup_string(target, "filename", &strptr))
strncpy(target_filename, strptr, sizeof(target_filename)-1);
//string_is_set for target_file_extension and target_filename must differ: only 1 can be set, not both set or both not set.
if ((target_file_extension[0]!=0) == (target_filename[0]!=0)) continue;
me = menuCreateEntry(ENTRY_TYPE_FILEASSOC);
success2 = 0;
if (me) {
strncpy(me->path, 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) {

View File

@ -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;
}

View File

@ -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);

View File

@ -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);