diff --git a/radio/src/gui/colorlcd/libui/file_browser.cpp b/radio/src/gui/colorlcd/libui/file_browser.cpp index 82656e9c01e..9d74ef09da7 100644 --- a/radio/src/gui/colorlcd/libui/file_browser.cpp +++ b/radio/src/gui/colorlcd/libui/file_browser.cpp @@ -29,22 +29,6 @@ #define CELL_CTRL_DIR LV_TABLE_CELL_CTRL_CUSTOM_1 #define CELL_CTRL_FILE LV_TABLE_CELL_CTRL_CUSTOM_2 -static const char* getFullPath(const char* filename) -{ - static char full_path[FF_MAX_LFN + 1]; - f_getcwd((TCHAR*)full_path, FF_MAX_LFN); - strcat(full_path, "/"); - strcat(full_path, filename); - return full_path; -} - -static const char* getCurrentPath() -{ - static char path[FF_MAX_LFN + 1]; - f_getcwd((TCHAR*)path, FF_MAX_LFN); - return path; -} - static int strnatcasecmp(char const *s1, char const *s2) { int i1, i2; @@ -138,10 +122,25 @@ static int scan_files(std::list& files, return 0; } +void FileBrowser::setFullPath(const char* name) +{ + if (currentPath == "/") + fullPathBuf = std::string("/") + name; + else + fullPathBuf = currentPath + "/" + name; +} + FileBrowser::FileBrowser(Window* parent, const rect_t& rect, const char* dir) : - TableField(parent, rect) + TableField(parent, rect), currentPath((dir && dir[0]) ? dir : "/") { - f_chdir(dir); + // Normalize once: drop any trailing slash (except root) so the join/parent + // logic never has to deal with it later. + while (currentPath.size() > 1 && currentPath.back() == '/') currentPath.pop_back(); + + if (f_chdir(currentPath.c_str()) != FR_OK) { + currentPath = "/"; // keep currentPath in sync with the FatFs CWD + f_chdir(currentPath.c_str()); + } setAutoEdit(); @@ -244,18 +243,25 @@ void FileBrowser::onSelected(const char* name, bool is_dir) return; } - const char* path = getCurrentPath(); - const char* fullpath = getFullPath(name); - if (fileSelected) fileSelected(path, name, fullpath, is_dir); + setFullPath(name); + if (fileSelected) fileSelected(currentPath.c_str(), name, fullPathBuf.c_str(), is_dir); selected = name; } void FileBrowser::onPress(const char* name, bool is_dir) { - const char* path = getCurrentPath(); - const char* fullpath = getFullPath(name); if (is_dir) { - f_chdir(fullpath); + std::string nextPath = currentPath; + if (strcmp(name, "..") == 0) { + // Go up: trim last segment (f_chdir("..") / f_getcwd are no-ops on exFAT). + auto pos = nextPath.find_last_of('/'); + nextPath = (pos == 0 || pos == std::string::npos) ? "/" : nextPath.substr(0, pos); + } else { + setFullPath(name); + nextPath = fullPathBuf; + } + // Only commit the tracked path if the CWD actually changed. + if (f_chdir(nextPath.c_str()) == FR_OK) currentPath = nextPath; if (fileSelected) fileSelected(nullptr, nullptr, nullptr, is_dir); selected = nullptr; refresh(); @@ -268,20 +274,19 @@ void FileBrowser::onPress(const char* name, bool is_dir) } if (fileAction){ - fileAction(path, name, fullpath, is_dir); + setFullPath(name); + fileAction(currentPath.c_str(), name, fullPathBuf.c_str(), is_dir); } } void FileBrowser::onPressLong(const char* name, bool is_dir) { - const char* path = getCurrentPath(); - const char* fullpath = getFullPath(name); - if (!selected || (selected != name)) { onSelected(name, is_dir); } if (fileAction){ - fileAction(path, name, fullpath, is_dir); + setFullPath(name); + fileAction(currentPath.c_str(), name, fullPathBuf.c_str(), is_dir); } } diff --git a/radio/src/gui/colorlcd/libui/file_browser.h b/radio/src/gui/colorlcd/libui/file_browser.h index e968a5f9fb9..190fe3bbefb 100644 --- a/radio/src/gui/colorlcd/libui/file_browser.h +++ b/radio/src/gui/colorlcd/libui/file_browser.h @@ -21,6 +21,8 @@ #pragma once +#include + #include "table.h" class FileBrowser : public TableField @@ -52,6 +54,12 @@ class FileBrowser : public TableField void onPressLong(const char* name, bool is_dir); private: + // Current dir, tracked explicitly because f_getcwd() is broken on exFAT. + std::string currentPath; + // Outlives the file-action call: callbacks capture the pointer for later use. + std::string fullPathBuf; + void setFullPath(const char* name); + const char* selected = nullptr; FileAction fileAction; FileAction fileSelected; diff --git a/radio/src/gui/colorlcd/radio/radio_sdmanager.cpp b/radio/src/gui/colorlcd/radio/radio_sdmanager.cpp index 611008772a1..a89ed07bb4a 100644 --- a/radio/src/gui/colorlcd/radio/radio_sdmanager.cpp +++ b/radio/src/gui/colorlcd/radio/radio_sdmanager.cpp @@ -467,14 +467,19 @@ void RadioSdManagerPage::fileAction(const char* path, const char* name, } menu->addLine(STR_COPY_FILE, [=]() { clipboard.type = CLIPBOARD_TYPE_SD_FILE; - f_getcwd(clipboard.data.sd.directory, CLIPBOARD_PATH_LEN); + // f_getcwd() is a no-op on exFAT; use the tracked path. + strncpy(clipboard.data.sd.directory, path, CLIPBOARD_PATH_LEN - 1); + clipboard.data.sd.directory[CLIPBOARD_PATH_LEN - 1] = '\0'; strncpy(clipboard.data.sd.filename, name, CLIPBOARD_PATH_LEN - 1); + clipboard.data.sd.filename[CLIPBOARD_PATH_LEN - 1] = '\0'; }); if (clipboard.type == CLIPBOARD_TYPE_SD_FILE) { menu->addLine(STR_PASTE, [=]() { static char lfn[FF_MAX_LFN + 1]; // TODO optimize that! char destFileName[2 * CLIPBOARD_PATH_LEN + 1]; - f_getcwd((TCHAR*)lfn, FF_MAX_LFN); + // f_getcwd() is a no-op on exFAT; use the tracked path. + strncpy(lfn, path, FF_MAX_LFN); + lfn[FF_MAX_LFN] = '\0'; // prevent copying to the same directory with the same name char* destNamePtr = clipboard.data.sd.filename; if (!strcmp(clipboard.data.sd.directory, lfn)) { diff --git a/radio/src/gui/common/stdlcd/radio_sdmanager.cpp b/radio/src/gui/common/stdlcd/radio_sdmanager.cpp index 4890cf004d2..480e23bb8f4 100644 --- a/radio/src/gui/common/stdlcd/radio_sdmanager.cpp +++ b/radio/src/gui/common/stdlcd/radio_sdmanager.cpp @@ -61,11 +61,31 @@ inline bool isFilenameLower(bool isfile, const char * fn, const char * line) return (!isfile && IS_FILE(line)) || (isfile==IS_FILE(line) && strcasecmp(fn, line) < 0); } +// Current dir, tracked explicitly: on exFAT f_getcwd() and f_chdir("..") are no-ops. +static char sdManagerPath[FF_MAX_LFN + 1] = "/"; + +static void sdManagerChdir(const char* name) +{ + if (!strcmp(name, "..")) { + // go up: drop the last path segment + char* sep = strrchr(sdManagerPath, '/'); + if (sep == sdManagerPath) sep[1] = '\0'; // parent is the root + else if (sep) *sep = '\0'; + } else { + // descend into 'name' (bounded, no duplicate slash at the root) + size_t len = strlen(sdManagerPath); + if (strcmp(sdManagerPath, ROOT_PATH) != 0 && len + 1 < sizeof(sdManagerPath)) + sdManagerPath[len++] = '/'; + strncpy(sdManagerPath + len, name, sizeof(sdManagerPath) - len - 1); + sdManagerPath[sizeof(sdManagerPath) - 1] = '\0'; + } + f_chdir(sdManagerPath); // absolute path: resolves on FAT and exFAT alike +} + void getSelectionFullPath(char * lfn) { - f_getcwd(lfn, FF_MAX_LFN); - strcat(lfn, "/"); - strcat(lfn, reusableBuffer.sdManager.lines[menuVerticalPosition - HEADER_LINE - menuVerticalOffset]); + snprintf(lfn, FF_MAX_LFN + 1, "%s/%s", sdManagerPath, + reusableBuffer.sdManager.lines[menuVerticalPosition - HEADER_LINE - menuVerticalOffset]); } #if defined(PXX2) @@ -121,12 +141,14 @@ void onSdManagerMenu(const char * result) } else if (result == STR_COPY_FILE) { clipboard.type = CLIPBOARD_TYPE_SD_FILE; - f_getcwd(clipboard.data.sd.directory, CLIPBOARD_PATH_LEN); + strncpy(clipboard.data.sd.directory, sdManagerPath, CLIPBOARD_PATH_LEN - 1); + clipboard.data.sd.directory[CLIPBOARD_PATH_LEN - 1] = '\0'; strncpy(clipboard.data.sd.filename, line, CLIPBOARD_PATH_LEN-1); + clipboard.data.sd.filename[CLIPBOARD_PATH_LEN - 1] = '\0'; } else if (result == STR_PASTE) { char destFileName[2 * CLIPBOARD_PATH_LEN + 1]; - f_getcwd(lfn, FF_MAX_LFN); + strcpy(lfn, sdManagerPath); // if destination is dir, copy into that dir if (IS_DIRECTORY(line)) { strcat(lfn, "/"); @@ -279,6 +301,7 @@ void menuRadioSdManager(event_t _event) if (_event == EVT_ENTRY) { f_chdir(ROOT_PATH); + strcpy(sdManagerPath, ROOT_PATH); #if LCD_DEPTH > 1 lastPos = -1; #endif @@ -325,7 +348,7 @@ void menuRadioSdManager(event_t _event) else { int index = menuVerticalPosition - HEADER_LINE - menuVerticalOffset; if (IS_DIRECTORY(reusableBuffer.sdManager.lines[index])) { - f_chdir(reusableBuffer.sdManager.lines[index]); + sdManagerChdir(reusableBuffer.sdManager.lines[index]); menuVerticalOffset = 0; menuVerticalPosition = HEADER_LINE; REFRESH_FILES(); diff --git a/radio/src/lib_file.cpp b/radio/src/lib_file.cpp index ec13d6b58b4..af02298d07d 100644 --- a/radio/src/lib_file.cpp +++ b/radio/src/lib_file.cpp @@ -83,16 +83,6 @@ bool isExtensionMatching(const char * extension, const char * pattern, char * ma return false; } -// returns true if current working dir is at the root level -bool isCwdAtRoot() -{ - char path[10]; - if (f_getcwd(path, sizeof(path)-1) == FR_OK) { - return (strcasecmp("/", path) == 0); - } - return false; -} - /* Wrapper around the f_readdir() function which also returns ".." entry for sub-dirs. (FatFS 0.12 does @@ -101,7 +91,10 @@ bool isCwdAtRoot() FRESULT sdReadDir(DIR * dir, FILINFO * fno, bool & firstTime) { FRESULT res; - if (firstTime && !isCwdAtRoot()) { + // Fake ".." only when browsing the non-root CWD itself (sclust == cdir, 0 == root). + // Filesystem-CWD based so it works on exFAT, where f_getcwd() always returns root; + // the sclust check keeps callers that open an arbitrary path (no chdir) unaffected. + if (firstTime && dir->obj.fs->cdir != 0 && dir->obj.sclust == dir->obj.fs->cdir) { // fake parent directory entry strcpy(fno->fname, ".."); fno->fattrib = AM_DIR;