diff --git a/workspace/all/common/defines.h b/workspace/all/common/defines.h index ba5bf08f..a551f1f5 100644 --- a/workspace/all/common/defines.h +++ b/workspace/all/common/defines.h @@ -23,6 +23,7 @@ #define BIN_PATH SYSTEM_PATH "/bin" #define TOOLS_PATH SDCARD_PATH "/Tools/" PLATFORM #define RECENT_PATH SHARED_USERDATA_PATH "/.minui/recent.txt" +#define SHORTCUTS_PATH SHARED_USERDATA_PATH "/.minui/shortcuts.txt" #define SIMPLE_MODE_PATH SHARED_USERDATA_PATH "/enable-simple-mode" #define AUTO_RESUME_PATH SHARED_USERDATA_PATH "/.minui/auto_resume.txt" #define AUTO_RESUME_SLOT 9 diff --git a/workspace/all/nextui/makefile b/workspace/all/nextui/makefile index a1f2365e..5c5d0751 100644 --- a/workspace/all/nextui/makefile +++ b/workspace/all/nextui/makefile @@ -21,7 +21,7 @@ SDL?=SDL TARGET = nextui INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/scaler.c ../common/utils.c ../common/config.c ../common/api.c ../../$(PLATFORM)/platform/platform.c +SOURCE = $(TARGET).c shortcuts.c ../common/scaler.c ../common/utils.c ../common/config.c ../common/api.c ../../$(PLATFORM)/platform/platform.c CC = $(CROSS_COMPILE)gcc CFLAGS += $(OPT) diff --git a/workspace/all/nextui/nextui.c b/workspace/all/nextui/nextui.c index c2aaefd3..ba2f5c5b 100644 --- a/workspace/all/nextui/nextui.c +++ b/workspace/all/nextui/nextui.c @@ -11,6 +11,7 @@ #include "api.h" #include "utils.h" #include "config.h" +#include "shortcuts.h" #include #include #include @@ -575,6 +576,21 @@ static int hasM3u(char* rom_path, char* m3u_path) { // NOTE: rom_path not dir_pa return exists(m3u_path); } +// Check if entry can be pinned (PAK, ROM, or multi-disc directory with cue/m3u) +static int canPinEntry(Entry* entry) { + // PAK and ROM can always be pinned + if (entry->type == ENTRY_PAK || entry->type == ENTRY_ROM) { + return 1; + } + // ENTRY_DIR can be pinned only if it has a .cue or .m3u file (multi-disc game) + if (entry->type == ENTRY_DIR) { + char cue_path[256]; + char m3u_path[256]; + return hasCue(entry->path, cue_path) || hasM3u(entry->path, m3u_path); + } + return 0; +} + static int hasRecents(void) { LOG_info("hasRecents %s\n", RECENT_PATH); int has = 0; @@ -869,6 +885,38 @@ static Array* getRoot(void) { } } + // Add shortcuts (after Recents and Collections, before user root folders) + if (Shortcuts_getCount() > 0 && !simple_mode) { + Shortcuts_validate(); + for (int i = 0; i < Shortcuts_getCount(); i++) { + char* path = Shortcuts_getPath(i); + char* name = Shortcuts_getName(i); + char sd_path[256]; + sprintf(sd_path, "%s%s", SDCARD_PATH, path); + + // Determine entry type based on path + int type; + if (suffixMatch(".pak", sd_path)) { + type = ENTRY_PAK; + } else { + DIR* dh = opendir(sd_path); + if (dh) { + closedir(dh); + type = ENTRY_DIR; + } else { + type = ENTRY_ROM; + } + } + + Entry* entry = Entry_new(sd_path, type); + if (name) { + free(entry->name); + entry->name = strdup(name); + } + Array_push(root, entry); + } + } + // Move entries to root Array_yoink(root, entries); @@ -1622,6 +1670,7 @@ static void QuickMenu_quit(void) { static void Menu_init(void) { stack = Array_new(); // array of open Directories recents = Array_new(); + Shortcuts_init(); openDirectory(SDCARD_PATH, 0); loadLast(); // restore state when available @@ -1630,6 +1679,7 @@ static void Menu_init(void) { } static void Menu_quit(void) { RecentArray_free(recents); + Shortcuts_quit(); DirectoryArray_free(stack); QuickMenu_quit(); @@ -1641,6 +1691,10 @@ static int dirty = 1; static int previous_row = 0; static int previous_depth = 0; +// Shortcut confirmation dialog state +static int confirm_shortcut_action = 0; // 0=none, 1=add, 2=remove +static Entry* confirm_shortcut_entry = NULL; + /////////////////////////////////////// enum { @@ -1947,6 +2001,7 @@ void onBackgroundLoaded(SDL_Surface* surface) { if (folderbgbmp) SDL_FreeSurface(folderbgbmp); if (!surface) { folderbgbmp = NULL; + setNeedDraw(1); SDL_UnlockMutex(bgMutex); return; } @@ -2467,12 +2522,12 @@ int main (int argc, char *argv[]) { folderbgchanged = 1; // The background painting code is a clusterfuck, just force a repaint here if (!HAS_POWER_BUTTON && !simple_mode) PWR_enableSleep(); } - else if (PAD_tappedSelect(now)) { + else if (PAD_tappedSelect(now) && confirm_shortcut_action == 0) { currentScreen = SCREEN_GAMESWITCHER; switcher_selected = 0; dirty = 1; } - else if (total>0) { + else if (total>0 && confirm_shortcut_action == 0) { if (PAD_justRepeated(BTN_UP)) { if (selected==0 && !PAD_justPressed(BTN_UP)) { // stop at top @@ -2537,7 +2592,7 @@ int main (int argc, char *argv[]) { } } - if (PAD_justRepeated(BTN_L1) && !PAD_isPressed(BTN_R1) && !PWR_ignoreSettingInput(BTN_L1, show_setting)) { // previous alpha + if (confirm_shortcut_action == 0 && PAD_justRepeated(BTN_L1) && !PAD_isPressed(BTN_R1) && !PWR_ignoreSettingInput(BTN_L1, show_setting)) { // previous alpha Entry* entry = top->entries->items[selected]; int i = entry->alpha-1; if (i>=0) { @@ -2550,7 +2605,7 @@ int main (int argc, char *argv[]) { } } } - else if (PAD_justRepeated(BTN_R1) && !PAD_isPressed(BTN_L1) && !PWR_ignoreSettingInput(BTN_R1, show_setting)) { // next alpha + else if (confirm_shortcut_action == 0 && PAD_justRepeated(BTN_R1) && !PAD_isPressed(BTN_L1) && !PWR_ignoreSettingInput(BTN_R1, show_setting)) { // next alpha Entry* entry = top->entries->items[selected]; int i = entry->alpha+1; if (ialphas->count) { @@ -2574,10 +2629,49 @@ int main (int argc, char *argv[]) { if (dirty && total>0) readyResume(entry); - if (total>0 && can_resume && PAD_justReleased(BTN_RESUME)) { + // Handle confirmation dialog for shortcuts + if (confirm_shortcut_action > 0) { + if (PAD_justPressed(BTN_A)) { + Shortcuts_confirmAction(confirm_shortcut_action, confirm_shortcut_entry); + confirm_shortcut_action = 0; + confirm_shortcut_entry = NULL; + + // Refresh root directory to show updated shortcuts + Directory* root = stack->items[0]; + EntryArray_free(root->entries); + root->entries = getRoot(); + IntArray_free(root->alphas); + root->alphas = IntArray_new(); + Directory_index(root); + // Keep selected in bounds + if (root->selected >= root->entries->count) { + root->selected = root->entries->count > 0 ? root->entries->count - 1 : 0; + } + + dirty = 1; + } + else if (PAD_justPressed(BTN_B)) { + confirm_shortcut_action = 0; + confirm_shortcut_entry = NULL; + dirty = 1; + } + } + else if (total>0 && can_resume && PAD_justReleased(BTN_RESUME)) { should_resume = 1; Entry_open(entry); - + + dirty = 1; + } + // Y to add/remove shortcut (only in Tools folder or console directory) + else if (total > 0 && + (Shortcuts_isInToolsFolder(top->path) || Shortcuts_isInConsoleDir(top->path)) && + canPinEntry(entry) && PAD_justReleased(BTN_Y)) { + if (Shortcuts_exists(entry->path + strlen(SDCARD_PATH))) { + confirm_shortcut_action = 2; // remove + } else { + confirm_shortcut_action = 1; // add + } + confirm_shortcut_entry = entry; dirty = 1; } else if (total>0 && PAD_justPressed(BTN_A)) { @@ -2958,22 +3052,50 @@ int main (int argc, char *argv[]) { char defaultBgPath[512]; snprintf(defaultBgPath, sizeof(defaultBgPath), SDCARD_PATH "/bg.png"); - if(((entry->type == ENTRY_DIR || entry->type == ENTRY_ROM) && CFG_getRomsUseFolderBackground())) { - char *newBg = entry->type == ENTRY_DIR ? entry->path:rompath; - if((strcmp(newBg, folderBgPath) != 0 || lastType != entry->type) && sizeof(folderBgPath) != 1) { + if(entry->type == ENTRY_DIR || entry->type == ENTRY_ROM) { + int is_shortcut = Shortcuts_exists(entry->path + strlen(SDCARD_PATH)); + if (is_shortcut) { + // Shortcut - clear background lastType = entry->type; - char tmppath[512]; - strncpy(folderBgPath, newBg, sizeof(folderBgPath) - 1); - if (entry->type == ENTRY_DIR) - snprintf(tmppath, sizeof(tmppath), "%s/.media/bg.png", folderBgPath); - else if (entry->type == ENTRY_ROM) - snprintf(tmppath, sizeof(tmppath), "%s/.media/bglist.png", folderBgPath); - if(!exists(tmppath)) { - // Safeguard: If no background is available, still render the text to leave the user a way out + strncpy(folderBgPath, entry->path, sizeof(folderBgPath) - 1); + onBackgroundLoaded(NULL); + GFX_clearLayers(LAYER_BACKGROUND); + list_show_entry_names = true; + } else if (CFG_getRomsUseFolderBackground()) { + // Not a shortcut - load folder background + char *newBg = entry->type == ENTRY_DIR ? entry->path : rompath; + if((strcmp(newBg, folderBgPath) != 0 || lastType != entry->type) && sizeof(folderBgPath) != 1) { + lastType = entry->type; + char tmppath[512]; + strncpy(folderBgPath, newBg, sizeof(folderBgPath) - 1); + if (entry->type == ENTRY_DIR) + snprintf(tmppath, sizeof(tmppath), "%s/.media/bg.png", folderBgPath); + else if (entry->type == ENTRY_ROM) + snprintf(tmppath, sizeof(tmppath), "%s/.media/bglist.png", folderBgPath); + if(!exists(tmppath)) { + // Safeguard: If no background is available, still render the text to leave the user a way out + list_show_entry_names = true; + snprintf(tmppath, sizeof(tmppath), defaultBgPath, folderBgPath); + } + startLoadFolderBackground(tmppath, onBackgroundLoaded, NULL); + } + } + } + // Handle PAK entries (tools and shortcuts) - load background from Tools/.media folder + else if(entry->type == ENTRY_PAK && suffixMatch(".pak", entry->path)) { + char tmppath[512]; + // Look for background in Tools/$PLATFORM/.media/PAK_NAME/bg.png + snprintf(tmppath, sizeof(tmppath), TOOLS_PATH "/.media/%s/bg.png", Shortcuts_getPakBasename(entry->path)); + if(strcmp(entry->path, folderBgPath) != 0 || lastType != entry->type) { + lastType = entry->type; + strncpy(folderBgPath, entry->path, sizeof(folderBgPath) - 1); + if(exists(tmppath)) { + startLoadFolderBackground(tmppath, onBackgroundLoaded, NULL); + } + else { + onBackgroundLoaded(NULL); list_show_entry_names = true; - snprintf(tmppath, sizeof(tmppath), defaultBgPath, folderBgPath); } - startLoadFolderBackground(tmppath, onBackgroundLoaded, NULL); } } else if(strcmp(defaultBgPath, folderBgPath) != 0 && exists(defaultBgPath)) { @@ -3007,6 +3129,11 @@ int main (int argc, char *argv[]) { // buttons if (show_setting && !GetHDMI()) GFX_blitHardwareHints(screen, show_setting); else if (can_resume) GFX_blitButtonGroup((char*[]){ "X","RESUME", NULL }, 0, screen, 0); + else if (total > 0 && (Shortcuts_isInToolsFolder(top->path) || Shortcuts_isInConsoleDir(top->path)) && + canPinEntry(entry)) { + char* label = Shortcuts_exists(entry->path + strlen(SDCARD_PATH)) ? "UNPIN" : "PIN"; + GFX_blitButtonGroup((char*[]){ "Y", label, NULL }, 0, screen, 0); + } else GFX_blitButtonGroup((char*[]){ BTN_SLEEP==BTN_POWER?"POWER":"MENU", BTN_SLEEP==BTN_POWER||simple_mode?"SLEEP":"INFO", @@ -3017,7 +3144,7 @@ int main (int argc, char *argv[]) { GFX_blitButtonGroup((char*[]){ "B","BACK", NULL }, 0, screen, 1); } } - else { + else if (confirm_shortcut_action == 0) { if (stack->count>1) { GFX_blitButtonGroup((char*[]){ "B","BACK", "A","OPEN", NULL }, 1, screen, 1); } @@ -3125,7 +3252,17 @@ int main (int argc, char *argv[]) { // TODO: for some reason screen's dimensions end up being 0x0 in GFX_blitMessage... GFX_blitMessage(font.large, "Empty folder", screen, &(SDL_Rect){0,0,screen->w,screen->h}); //, NULL); } - + + // Render confirmation dialog for shortcuts + if (confirm_shortcut_action > 0 && confirm_shortcut_entry) { + char message[256]; + char* fmt = confirm_shortcut_action == 1 ? "Pin \"%s\"?" : "Unpin \"%s\"?"; + snprintf(message, sizeof(message), fmt, confirm_shortcut_entry->name); + SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 0, 0, 0)); + GFX_blitMessage(font.large, message, screen, &(SDL_Rect){0, 0, screen->w, screen->h}); + GFX_blitButtonGroup((char*[]){ "B","CANCEL","A","CONFIRM", NULL }, 0, screen, 1); + } + lastScreen = SCREEN_GAMELIST; } @@ -3169,7 +3306,12 @@ int main (int argc, char *argv[]) { } SDL_UnlockMutex(bgMutex); SDL_LockMutex(thumbMutex); - if(thumbbmp && thumbchanged) { + // Hide thumbnail and scrolling text when confirmation dialog is shown + if (confirm_shortcut_action > 0) { + GFX_clearLayers(LAYER_THUMBNAIL); + GFX_clearLayers(LAYER_SCROLLTEXT); + } + else if(thumbbmp && thumbchanged) { int img_w = thumbbmp->w; int img_h = thumbbmp->h; double aspect_ratio = (double)img_h / img_w; @@ -3177,7 +3319,7 @@ int main (int argc, char *argv[]) { int max_h = (int)(screen->h * 0.6); int new_w = max_w; int new_h = (int)(new_w * aspect_ratio); - + if (new_h > max_h) { new_h = max_h; new_w = (int)(new_h / aspect_ratio); @@ -3222,22 +3364,27 @@ int main (int argc, char *argv[]) { } SDL_UnlockMutex(bgMutex); SDL_LockMutex(thumbMutex); - if(thumbbmp && thumbchanged) { + // Hide thumbnail and scrolling text when confirmation dialog is shown + if (confirm_shortcut_action > 0) { + GFX_clearLayers(LAYER_THUMBNAIL); + GFX_clearLayers(LAYER_SCROLLTEXT); + } + else if(thumbbmp && thumbchanged) { int img_w = thumbbmp->w; int img_h = thumbbmp->h; double aspect_ratio = (double)img_h / img_w; - - int max_w = (int)(screen->w * CFG_getGameArtWidth()); - int max_h = (int)(screen->h * 0.6); + + int max_w = (int)(screen->w * CFG_getGameArtWidth()); + int max_h = (int)(screen->h * 0.6); int new_w = max_w; - int new_h = (int)(new_w * aspect_ratio); - + int new_h = (int)(new_w * aspect_ratio); + if (new_h > max_h) { new_h = max_h; new_w = (int)(new_h / aspect_ratio); } - + int target_x = screen->w-(new_w + SCALE1(BUTTON_MARGIN*3)); int target_y = (int)(screen->h * 0.50); int center_y = target_y - (new_h / 2); // FIX: use new_h instead of thumbbmp->h @@ -3258,7 +3405,8 @@ int main (int argc, char *argv[]) { } SDL_UnlockMutex(animMutex); if (currentScreen != SCREEN_GAMESWITCHER && currentScreen != SCREEN_QUICKMENU) { - if(is_scrolling && pillanimdone && currentAnimQueueSize < 1) { + // Skip scrolling text when confirmation dialog is shown + if(is_scrolling && pillanimdone && currentAnimQueueSize < 1 && confirm_shortcut_action == 0) { int ow = GFX_blitHardwareGroup(screen, show_setting); Entry* entry = top->entries->items[top->selected]; trimSortingMeta(&entry->name); diff --git a/workspace/all/nextui/shortcuts.c b/workspace/all/nextui/shortcuts.c new file mode 100644 index 00000000..abf3c9a4 --- /dev/null +++ b/workspace/all/nextui/shortcuts.c @@ -0,0 +1,303 @@ +#include "shortcuts.h" +#include "defines.h" +#include "utils.h" +#include +#include +#include + +/////////////////////////////////////// +// Internal types + +typedef struct Shortcut { + char* path; // without SDCARD_PATH prefix + char* name; // display name +} Shortcut; + +typedef struct Array { + int count; + int capacity; + void** items; +} Array; + +// Entry struct (must match nextui.c) +struct Entry { + char* path; + char* name; + char* unique; + int type; + int alpha; +}; + +/////////////////////////////////////// +// Internal Array functions + +static Array* Array_new(void) { + Array* self = malloc(sizeof(Array)); + self->count = 0; + self->capacity = 8; + self->items = malloc(sizeof(void*) * self->capacity); + return self; +} + +static void Array_push(Array* self, void* item) { + if (self->count >= self->capacity) { + self->capacity *= 2; + self->items = realloc(self->items, sizeof(void*) * self->capacity); + } + self->items[self->count++] = item; +} + +static void* Array_pop(Array* self) { + if (self->count == 0) return NULL; + return self->items[--self->count]; +} + +static void Array_remove(Array* self, void* item) { + if (self->count == 0 || item == NULL) return; + int i = 0; + while (self->items[i] != item) i++; + for (int j = i; j < self->count - 1; j++) { + self->items[j] = self->items[j + 1]; + } + self->count--; +} + +static void Array_free(Array* self) { + free(self->items); + free(self); +} + +/////////////////////////////////////// +// Shortcut functions + +static Shortcut* Shortcut_new(char* path, char* name) { + Shortcut* self = malloc(sizeof(Shortcut)); + self->path = strdup(path); + self->name = name ? strdup(name) : NULL; + return self; +} + +static void Shortcut_free(Shortcut* self) { + free(self->path); + if (self->name) free(self->name); + free(self); +} + +static int ShortcutArray_indexOf(Array* self, char* path) { + for (int i = 0; i < self->count; i++) { + Shortcut* shortcut = self->items[i]; + if (exactMatch(shortcut->path, path)) return i; + } + return -1; +} + +static void ShortcutArray_free(Array* self) { + for (int i = 0; i < self->count; i++) { + Shortcut_free(self->items[i]); + } + Array_free(self); +} + +static int ShortcutCompare(const void* a, const void* b) { + Shortcut* sa = *(Shortcut**)a; + Shortcut* sb = *(Shortcut**)b; + // Compare by name (case-insensitive) + return strcasecmp(sa->name ? sa->name : sa->path, sb->name ? sb->name : sb->path); +} + +static void ShortcutArray_sort(Array* self) { + if (self && self->count > 1) { + qsort(self->items, self->count, sizeof(void*), ShortcutCompare); + } +} + +/////////////////////////////////////// +// Global state + +static Array* shortcuts = NULL; + +/////////////////////////////////////// +// Save/Load functions + +static void saveShortcuts(void) { + FILE* file = fopen(SHORTCUTS_PATH, "w"); + if (file) { + for (int i = 0; i < shortcuts->count; i++) { + Shortcut* shortcut = shortcuts->items[i]; + fputs(shortcut->path, file); + if (shortcut->name) { + fputs("\t", file); + fputs(shortcut->name, file); + } + putc('\n', file); + } + fclose(file); + } +} + +static int loadShortcuts(void) { + if (shortcuts) { + ShortcutArray_free(shortcuts); + } + shortcuts = Array_new(); + int removed_any = 0; + + FILE* file = fopen(SHORTCUTS_PATH, "r"); + if (file) { + char line[256]; + while (fgets(line, 256, file) != NULL) { + normalizeNewline(line); + trimTrailingNewlines(line); + if (strlen(line) == 0) continue; + + char* path = line; + char* name = NULL; + char* tmp = strchr(line, '\t'); + if (tmp) { + tmp[0] = '\0'; + name = tmp + 1; + } + + // Validate that the tool still exists + char sd_path[256]; + sprintf(sd_path, "%s%s", SDCARD_PATH, path); + + if (exists(sd_path)) { + Array_push(shortcuts, Shortcut_new(path, name)); + } else { + removed_any = 1; + } + } + fclose(file); + } + + // Sort alphabetically + ShortcutArray_sort(shortcuts); + + // Auto-clean: re-save if any were removed + if (removed_any) saveShortcuts(); + + return shortcuts->count > 0; +} + +/////////////////////////////////////// +// Public API + +void Shortcuts_init(void) { + shortcuts = Array_new(); + loadShortcuts(); +} + +void Shortcuts_quit(void) { + if (shortcuts) { + ShortcutArray_free(shortcuts); + shortcuts = NULL; + } +} + +int Shortcuts_exists(char* path) { + if (!shortcuts) return 0; + return ShortcutArray_indexOf(shortcuts, path) != -1; +} + +void Shortcuts_add(Entry* entry) { + if (!shortcuts || !entry) return; + + char* path = entry->path + strlen(SDCARD_PATH); + if (Shortcuts_exists(path)) return; + + while (shortcuts->count >= MAX_SHORTCUTS) { + Shortcut_free(Array_pop(shortcuts)); + } + Array_push(shortcuts, Shortcut_new(path, entry->name)); + ShortcutArray_sort(shortcuts); + saveShortcuts(); +} + +void Shortcuts_remove(Entry* entry) { + if (!shortcuts || !entry) return; + + char* path = entry->path + strlen(SDCARD_PATH); + int idx = ShortcutArray_indexOf(shortcuts, path); + if (idx != -1) { + Shortcut* shortcut = shortcuts->items[idx]; + Array_remove(shortcuts, shortcut); + Shortcut_free(shortcut); + saveShortcuts(); + } +} + +int Shortcuts_isInToolsFolder(char* path) { + char tools_path[256]; + snprintf(tools_path, sizeof(tools_path), "%s/Tools/%s", SDCARD_PATH, PLATFORM); + return prefixMatch(tools_path, path); +} + +int Shortcuts_isInConsoleDir(char* path) { + char parent_dir[256]; + strncpy(parent_dir, path, sizeof(parent_dir) - 1); + parent_dir[sizeof(parent_dir) - 1] = '\0'; + char* last_slash = strrchr(parent_dir, '/'); + if (last_slash) *last_slash = '\0'; + return exactMatch(parent_dir, ROMS_PATH); +} + +int Shortcuts_getCount(void) { + return shortcuts ? shortcuts->count : 0; +} + +char* Shortcuts_getPath(int index) { + if (!shortcuts || index < 0 || index >= shortcuts->count) return NULL; + Shortcut* shortcut = shortcuts->items[index]; + return shortcut->path; +} + +char* Shortcuts_getName(int index) { + if (!shortcuts || index < 0 || index >= shortcuts->count) return NULL; + Shortcut* shortcut = shortcuts->items[index]; + return shortcut->name; +} + +int Shortcuts_validate(void) { + if (!shortcuts) return 0; + + int needs_save = 0; + for (int i = shortcuts->count - 1; i >= 0; i--) { + Shortcut* shortcut = shortcuts->items[i]; + char sd_path[256]; + sprintf(sd_path, "%s%s", SDCARD_PATH, shortcut->path); + + if (!exists(sd_path)) { + Array_remove(shortcuts, shortcut); + Shortcut_free(shortcut); + needs_save = 1; + } + } + + if (needs_save) saveShortcuts(); + return needs_save; +} + +char* Shortcuts_getPakBasename(const char* path) { + static char basename[256]; + + // Extract filename from path + const char* pakname = strrchr(path, '/'); + pakname = pakname ? pakname + 1 : path; + + // Copy and remove .pak extension + strncpy(basename, pakname, sizeof(basename) - 1); + basename[sizeof(basename) - 1] = '\0'; + char* dot = strrchr(basename, '.'); + if (dot) *dot = '\0'; + + return basename; +} + +void Shortcuts_confirmAction(int action, Entry* entry) { + if (action == 1) { + Shortcuts_add(entry); + } else { + Shortcuts_remove(entry); + } +} diff --git a/workspace/all/nextui/shortcuts.h b/workspace/all/nextui/shortcuts.h new file mode 100644 index 00000000..db8f653f --- /dev/null +++ b/workspace/all/nextui/shortcuts.h @@ -0,0 +1,56 @@ +#ifndef SHORTCUTS_H +#define SHORTCUTS_H + +#include +#include +#include +#include "defines.h" +#include "utils.h" + +#define MAX_SHORTCUTS 12 + +// Forward declarations +typedef struct Entry Entry; +typedef struct Array Array; + +// Initialize shortcuts (call in Menu_init) +void Shortcuts_init(void); + +// Cleanup shortcuts (call in Menu_quit) +void Shortcuts_quit(void); + +// Check if a shortcut exists for the given path (without SDCARD_PATH prefix) +int Shortcuts_exists(char* path); + +// Add a shortcut for the given entry +void Shortcuts_add(Entry* entry); + +// Remove a shortcut for the given entry +void Shortcuts_remove(Entry* entry); + +// Check if inside Tools folder +int Shortcuts_isInToolsFolder(char* path); + +// Check if inside a console directory (parent is ROMS_PATH) +int Shortcuts_isInConsoleDir(char* path); + +// Get shortcuts count +int Shortcuts_getCount(void); + +// Get shortcut path at index (without SDCARD_PATH prefix) +char* Shortcuts_getPath(int index); + +// Get shortcut name at index +char* Shortcuts_getName(int index); + +// Validate and clean up stale shortcuts (returns 1 if any were removed) +int Shortcuts_validate(void); + +// Extract PAK basename from path (e.g., "/path/to/Retroarch.pak" -> "Retroarch") +// Returns pointer to static buffer, caller should copy if needed +char* Shortcuts_getPakBasename(const char* path); + +// Handle confirmation action (1=add, 2=remove) and reboot +void Shortcuts_confirmAction(int action, Entry* entry); + +#endif // SHORTCUTS_H