From 5f8f40e5923fb0cfce9571f1a96abd4a05a193a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 29 Mar 2025 08:43:30 +0100 Subject: [PATCH] Add file browser support on Linux through portable-file-dialogs --- Common/System/Request.h | 1 + SDL/SDLMain.cpp | 91 ++++++++++++++++++++++++++++++++-- UI/DarwinFileSystemServices.mm | 18 ++++--- UWP/PPSSPP_UWPMain.cpp | 5 +- Windows/main.cpp | 2 + 5 files changed, 106 insertions(+), 11 deletions(-) diff --git a/Common/System/Request.h b/Common/System/Request.h index 5e843c9546..9d408ca39c 100644 --- a/Common/System/Request.h +++ b/Common/System/Request.h @@ -104,6 +104,7 @@ enum class BrowseFileType { SOUND_EFFECT, ZIP, SYMBOL_MAP, + SYMBOL_MAP_NOCASH, ATRAC3, ANY, }; diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index 537f250762..20ee66c580 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -24,6 +24,8 @@ SDLJoystick *joystick = NULL; #include #include +#include "ext/portable-file-dialogs/portable-file-dialogs.h" + #include "Common/System/Display.h" #include "Common/System/System.h" #include "Common/System/Request.h" @@ -221,6 +223,47 @@ void System_Vibrate(int length_ms) { // Ignore on PC } +static void InitializeFilters(std::vector &filters, BrowseFileType type) { + switch (type) { + case BrowseFileType::BOOTABLE: + filters.push_back("All supported file types (*.iso *.cso *.chd *.pbp *.elf *.prx *.zip *.ppdmp)"); + filters.push_back("*.pbp *.elf *.iso *.cso *.chd *.prx *.zip *.ppdmp"); + break; + case BrowseFileType::INI: + filters.push_back("Ini files"); + filters.push_back("*.ini"); + break; + case BrowseFileType::ZIP: + filters.push_back("ZIP files"); + filters.push_back("*.zip"); + break; + case BrowseFileType::DB: + filters.push_back("Cheat db files"); + filters.push_back("*.db"); + break; + case BrowseFileType::SOUND_EFFECT: + filters.push_back("Sound effect files (wav, mp3)"); + filters.push_back("*.wav *.mp3"); + break; + case BrowseFileType::SYMBOL_MAP: + filters.push_back("PPSSPP Symbol Map files (ppmap)"); + filters.push_back("*.ppmap"); + break; + case BrowseFileType::SYMBOL_MAP_NOCASH: + filters.push_back("No$ symbol Map files (sym)"); + filters.push_back("*.sym"); + break; + case BrowseFileType::ATRAC3: + filters.push_back("Atrac3 files (at3)"); + filters.push_back("*.at3"); + break; + case BrowseFileType::ANY: + break; + } + filters.push_back("All files (*.*)"); + filters.push_back("*"); +} + bool System_MakeRequest(SystemRequestType type, int requestId, const std::string ¶m1, const std::string ¶m2, int64_t param3, int64_t param4) { switch (type) { case SystemRequestType::RESTART_APP: @@ -288,6 +331,44 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string DarwinFileSystemServices::presentDirectoryPanel(callback, /* allowFiles = */ false, /* allowDirectories = */ true); return true; } +#else + case SystemRequestType::BROWSE_FOR_FILE: + case SystemRequestType::BROWSE_FOR_FILE_SAVE: + { + // TODO: Add non-blocking support. + const BrowseFileType browseType = (BrowseFileType)param3; + std::string initialFilename = param2; + const std::string &title = param1; + std::vector filters; + InitializeFilters(filters, browseType); + if (type == SystemRequestType::BROWSE_FOR_FILE) { + std::vector result = pfd::open_file(title, initialFilename, filters).result(); + if (!result.empty()) { + g_requestManager.PostSystemSuccess(requestId, result[0]); + } else { + g_requestManager.PostSystemFailure(requestId); + } + } else { + std::string result = pfd::save_file(title, initialFilename, filters).result(); + if (!result.empty()) { + g_requestManager.PostSystemSuccess(requestId, result); + } else { + g_requestManager.PostSystemFailure(requestId); + } + } + return true; + } + case SystemRequestType::BROWSE_FOR_FOLDER: + { + // TODO: Add non-blocking support. + std::string result = pfd::select_folder(param1, param2).result(); + if (!result.empty()) { + g_requestManager.PostSystemSuccess(requestId, result); + } else { + g_requestManager.PostSystemFailure(requestId); + } + return true; + } #endif case SystemRequestType::TOGGLE_FULLSCREEN_STATE: { @@ -606,16 +687,18 @@ bool System_GetPropertyBool(SystemProperty prop) { #endif case SYSPROP_CAN_JIT: return true; - case SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR: + case SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR: return true; // FileUtil.cpp: OpenFileInEditor #ifndef HTTPS_NOT_AVAILABLE case SYSPROP_SUPPORTS_HTTPS: return !g_Config.bDisableHTTPS; #endif +case SYSPROP_HAS_FOLDER_BROWSER: +case SYSPROP_HAS_FILE_BROWSER: #if PPSSPP_PLATFORM(MAC) - case SYSPROP_HAS_FOLDER_BROWSER: - case SYSPROP_HAS_FILE_BROWSER: return true; +#else + return pfd::settings::available(); #endif case SYSPROP_HAS_ACCELEROMETER: #if defined(MOBILE_DEVICE) @@ -1501,7 +1584,7 @@ int main(int argc, char *argv[]) { graphicsContext->ThreadStart(); InputStateTracker inputTracker{}; - + #if PPSSPP_PLATFORM(MAC) // setup menu items for macOS initializeOSXExtras(); diff --git a/UI/DarwinFileSystemServices.mm b/UI/DarwinFileSystemServices.mm index 3458eb2149..98391dec75 100644 --- a/UI/DarwinFileSystemServices.mm +++ b/UI/DarwinFileSystemServices.mm @@ -88,7 +88,13 @@ void DarwinFileSystemServices::presentDirectoryPanel( [panel setAllowedFileTypes:[NSArray arrayWithObject:@"db"]]; break; case BrowseFileType::SOUND_EFFECT: - [panel setAllowedFileTypes:[NSArray arrayWithObject:@"wav"]]; + [panel setAllowedFileTypes:[NSArray arrayWithObject:@"wav", @"mp3"]]; + break; + case BrowseFileType::SYMBOL_MAP: + [panel setAllowedFileTypes:[NSArray arrayWithObject:@"ppsym"]]; + break; + case BrowseFileType::SYMBOL_MAP_NOCASH: + [panel setAllowedFileTypes:[NSArray arrayWithObject:@"sym"]]; break; case BrowseFileType::ATRAC3: [panel setAllowedFileTypes:[NSArray arrayWithObject:@"at3"]]; @@ -113,21 +119,21 @@ void DarwinFileSystemServices::presentDirectoryPanel( UIViewController *rootViewController = UIApplication.sharedApplication .keyWindow .rootViewController; - + // get current window view controller if (!rootViewController) return; - + NSMutableArray *types = [NSMutableArray array]; UIDocumentPickerMode pickerMode = UIDocumentPickerModeOpen; - + if (allowDirectories) [types addObject: (__bridge NSString *)kUTTypeFolder]; if (allowFiles) { [types addObject: (__bridge NSString *)kUTTypeItem]; pickerMode = UIDocumentPickerModeImport; } - + UIDocumentPickerViewController *pickerVC = [[UIDocumentPickerViewController alloc] initWithDocumentTypes: types inMode: pickerMode]; // What if you wanted to go to heaven, but then God showed you the next few lines? // serious note: have to do this, because __pickerDelegate has to stay retained as a class property @@ -142,7 +148,7 @@ Path DarwinFileSystemServices::appropriateMemoryStickDirectoryToUse() { NSString *userPreferred = [[NSUserDefaults standardUserDefaults] stringForKey:@(PreferredMemoryStickUserDefaultsKey)]; if (userPreferred) return Path(userPreferred.UTF8String); - + return defaultMemoryStickPath(); } diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp index 223a1e41a5..a03e8cb137 100644 --- a/UWP/PPSSPP_UWPMain.cpp +++ b/UWP/PPSSPP_UWPMain.cpp @@ -436,7 +436,7 @@ bool System_GetPropertyBool(SystemProperty prop) { return true; case SYSPROP_HAS_KEYBOARD: { - // Do actual check + // Do actual check // touch devices has input pane, we need to depend on it // I don't know any possible way to display input dialog in non-xaml apps return isKeyboardAvailable() || isTouchAvailable(); @@ -519,6 +519,9 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string case BrowseFileType::SYMBOL_MAP: supportedExtensions = { ".ppmap" }; break; + case BrowseFileType::SYMBOL_MAP_NOCASH: + supportedExtensions = { ".sym" }; + break; case BrowseFileType::DB: supportedExtensions = { ".db" }; break; diff --git a/Windows/main.cpp b/Windows/main.cpp index f9ede5e66f..21e249d588 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -539,6 +539,8 @@ static std::wstring MakeWindowsFilter(BrowseFileType type) { return FinalizeFilter(L"Sound effect files (*.wav *.mp3)|*.wav;*.mp3|All files (*.*)|*.*||"); case BrowseFileType::SYMBOL_MAP: return FinalizeFilter(L"Symbol map files (*.ppmap)|*.ppmap|All files (*.*)|*.*||"); + case BrowseFileType::SYMBOL_MAP_NOCASH: + return FinalizeFilter(L"No$ symbol map files (*.sym)|*.sym|All files (*.*)|*.*||"); case BrowseFileType::ATRAC3: return FinalizeFilter(L"ATRAC3/3+ files (*.at3)|*.at3|All files (*.*)|*.*||"); case BrowseFileType::ANY: