From b5815bde22b83a6a148175eaa89cc916ad850563 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Tue, 1 Jan 2019 10:58:53 -0600 Subject: [PATCH 1/6] NFD updated to 1.1.4 --- Engine/lib/nativeFileDialogs/README.md | 80 ++++-- Engine/lib/nativeFileDialogs/nfd_cocoa.m | 16 +- Engine/lib/nativeFileDialogs/nfd_gtk.c | 2 +- Engine/lib/nativeFileDialogs/nfd_win.cpp | 103 ++++--- Engine/lib/nativeFileDialogs/nfd_zenity.c | 311 +++++++++++++++++++++ Engine/lib/nativeFileDialogs/simple_exec.h | 214 ++++++++++++++ 6 files changed, 658 insertions(+), 68 deletions(-) create mode 100644 Engine/lib/nativeFileDialogs/nfd_zenity.c create mode 100644 Engine/lib/nativeFileDialogs/simple_exec.h diff --git a/Engine/lib/nativeFileDialogs/README.md b/Engine/lib/nativeFileDialogs/README.md index 7ff1008a3..83238e133 100644 --- a/Engine/lib/nativeFileDialogs/README.md +++ b/Engine/lib/nativeFileDialogs/README.md @@ -1,6 +1,6 @@ # Native File Dialog # -A tiny, neat C library that portably invokes native file open and save dialogs. Write dialog code once and have it pop up native dialogs on all supported platforms. Avoid linking large dependencies like wxWidgets and qt. +A tiny, neat C library that portably invokes native file open, folder select and save dialogs. Write dialog code once and have it pop up native dialogs on all supported platforms. Avoid linking large dependencies like wxWidgets and qt. Features: @@ -11,11 +11,12 @@ Features: - Paid support available. - Multiple file selection support. - 64-bit and 32-bit friendly. - - GCC, Clang and Visual Studio supported. - - No third party dependencies. + - GCC, Clang, Xcode, Mingw and Visual Studio supported. + - No third party dependencies for building or linking. - Support for Vista's modern `IFileDialog` on Windows. - Support for non-deprecated Cocoa APIs on OS X. - - GTK+3 dialog on Linux. + - GTK3 dialog on Linux. + - Optional Zenity support on Linux to avoid linking GTK. - Tested, works alongside [http://www.libsdl.org](SDL2) on all platforms, for the game developers out there. # Example Usage # @@ -50,47 +51,74 @@ See [NFD.h](src/include/nfd.h) for more options. # Screenshots # -![Windows 8 rendering an IFileOpenDialog](screens/open_win8.png?raw=true) +![Windows 8 rendering an IFileOpenDialog](screens/open_win.png?raw=true) ![GTK3 on Linux](screens/open_gtk3.png?raw=true) ![Cocoa on Yosemite](screens/open_cocoa.png?raw=true) +## Changelog ## + +release | what's new | date +--------|-----------------------------|--------- +1.0.0 | initial | oct 2014 +1.1.0 | premake5; scons deprecated | aug 2016 +1.1.1 | mingw support, build fixes | aug 2016 +1.1.2 | test_pickfolder() added | aug 2016 +1.1.3 | zenity linux backend added | nov 2017 +1.1.3 | fix char type in decls | nov 2017 +1.1.4 | fix win32 memleaks | dec 2018 +1.1.4 | improve win32 errorhandling | dec 2018 +1.1.4 | macos fix focus bug | dec 2018 + ## Building ## -NFD uses [SCons](http://www.scons.org) for cross-platform builds. After installing SCons, build it with: +NFD uses [Premake5](https://premake.github.io/download.html) generated Makefiles and IDE project files. The generated project files are checked in under `build/` so you don't have to download and use Premake in most cases. - cd src - scons debug=[0,1] +If you need to run Premake5 directly, further [build documentation](docs/build.md) is available. -Alternatively, you can avoid Scons by just including NFD files to your existing project: +Previously, NFD used SCons to build. It still works, but is now deprecated; updates to it are discouraged. Opt to use the native build system where possible. - 1. Add all header files in `src/` and `src/include` to your project. - 2. Add `src/include` to your include search path or copy it into your existing search path. - 3. Add `src/nfd_common.c` to your project. - 4. Add `src/nfd_` to your project, where `` is the NFD backend for the platform you are fixing to build. - 5. On Visual Studio, define `_CRT_SECURE_NO_WARNINGS` to avoid warnings. +`nfd.a` will be built for release builds, and `nfd_d.a` will be built for debug builds. + +### Makefiles ### + +The makefile offers five options, with `release_x64` as the default. + + make config=release_x86 + make config=release_x64 + make config=debug_x86 + make config=debug_x64 ### Compiling Your Programs ### 1. Add `src/include` to your include search path. - 2. Add `nfd.lib` to the list of list of static libraries to link against. - 3. Add `src/` to the library search path. + 2. Add `nfd.lib` or `nfd_d.lib` to the list of list of static libraries to link against (for release or debug, respectively). + 3. Add `build//` to the library search path. -On Linux, you must compile and link against GTK+. Recommend use of `pkg-config --cflags --libs gtk+-3.0`. +#### Linux GTK #### +`apt-get libgtk-3-dev` installs the gtk dependency for library compilation. -On Mac OS X, add `AppKit` to the list of frameworks. +On Linux, you have the option of compiling and linking against GTK. If you use it, the recommended way to compile is to include the arguments of `pkg-config --cflags --libs gtk+-3.0`. +#### Linux Zenity #### + +Alternatively, you can use the Zenity backend by running the Makefile in `build/gmake_linux_zenity`. Zenity runs the dialog in its own address space, but requires the user to have Zenity correctly installed and configured on their system. + +#### MacOS #### +On Mac OS, add `AppKit` to the list of frameworks. + +#### Windows #### On Windows, ensure you are building against `comctl32.lib`. ## Usage ## See `NFD.h` for API calls. See `tests/*.c` for example code. -See `tests/SConstruct` for a working build script that compiles on all platforms. +After compiling, `build/bin` contains compiled test programs. ## File Filter Syntax ## -There is a form of file filtering in every file dialog, but no consistent means of supporting it. NFD provides support for filtering files by groups of extensions, providing its own descriptions (where applicable) for the extensions. +There is a form of file filtering in every file dialog API, but no consistent means of supporting it. NFD provides support for filtering files by groups of extensions, providing its own descriptions (where applicable) for the extensions. A wildcard filter is always added to every dialog. @@ -113,16 +141,14 @@ See [test_opendialogmultiple.c](test/test_opendialogmultiple.c). # Known Limitations # -I accept quality code patches, or will resolve these and other matters through support. +I accept quality code patches, or will resolve these and other matters through support. See [submitting pull requests](docs/submitting_pull_requests.md) for details. - No support for Windows XP's legacy dialogs such as `GetOpenFileName`. - - No support for file filter names -- ex: "Image Files" (*.png, *.jpg). Nameless filters are supported, though. - - No support for selecting folders instead of files. - - On Linux, GTK+ cannot be uninitialized to save memory. Launching a file dialog costs memory. I am open to accepting an alternative `nfd_zenity.c` implementation which uses Zenity and pipes. + - No support for file filter names -- ex: "Image Files" (*.png, *.jpg). Nameless filters are supported, however. # Copyright and Credit # -Copyright © 2014 [Frogtoss Games](http://www.frogtoss.com), Inc. +Copyright © 2014-2017 [Frogtoss Games](http://www.frogtoss.com), Inc. File [LICENSE](LICENSE) covers all files in this repo. Native File Dialog by Michael Labbe @@ -130,6 +156,10 @@ Native File Dialog by Michael Labbe Tomasz Konojacki for [microutf8](http://puszcza.gnu.org.ua/software/microutf8/) +[Denis Kolodin](https://github.com/DenisKolodin) for mingw support. + +[Tom Mason](https://github.com/wheybags) for Zenity support. + ## Support ## Directed support for this work is available from the original author under a paid agreement. diff --git a/Engine/lib/nativeFileDialogs/nfd_cocoa.m b/Engine/lib/nativeFileDialogs/nfd_cocoa.m index d3fb48347..39a0931de 100644 --- a/Engine/lib/nativeFileDialogs/nfd_cocoa.m +++ b/Engine/lib/nativeFileDialogs/nfd_cocoa.m @@ -117,7 +117,7 @@ static nfdresult_t AllocPathSet( NSArray *urls, nfdpathset_t *pathset ) /* public */ -nfdresult_t NFD_OpenDialog( const char *filterList, +nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList, const nfdchar_t *defaultPath, nfdchar_t **outPath ) { @@ -146,6 +146,7 @@ nfdresult_t NFD_OpenDialog( const char *filterList, if ( !*outPath ) { [pool release]; + [keyWindow makeKeyAndOrderFront:nil]; return NFD_ERROR; } memcpy( *outPath, utf8Path, len+1 ); /* copy null term */ @@ -163,6 +164,7 @@ nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList, nfdpathset_t *outPaths ) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; NSOpenPanel *dialog = [NSOpenPanel openPanel]; [dialog setAllowsMultipleSelection:YES]; @@ -181,12 +183,14 @@ nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList, if ( [urls count] == 0 ) { [pool release]; + [keyWindow makeKeyAndOrderFront:nil]; return NFD_CANCEL; } if ( AllocPathSet( urls, outPaths ) == NFD_ERROR ) { [pool release]; + [keyWindow makeKeyAndOrderFront:nil]; return NFD_ERROR; } @@ -194,6 +198,7 @@ nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList, } [pool release]; + [keyWindow makeKeyAndOrderFront:nil]; return nfdResult; } @@ -203,7 +208,8 @@ nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList, nfdchar_t **outPath ) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - + NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; + NSSavePanel *dialog = [NSSavePanel savePanel]; [dialog setExtensionHidden:NO]; @@ -225,6 +231,7 @@ nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList, if ( !*outPath ) { [pool release]; + [keyWindow makeKeyAndOrderFront:nil]; return NFD_ERROR; } memcpy( *outPath, utf8Path, byteLen ); @@ -232,7 +239,7 @@ nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList, } [pool release]; - + [keyWindow makeKeyAndOrderFront:nil]; return nfdResult; } @@ -241,7 +248,7 @@ nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath, { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; + NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; NSOpenPanel *dialog = [NSOpenPanel openPanel]; [dialog setAllowsMultipleSelection:NO]; [dialog setCanChooseDirectories:YES]; @@ -264,6 +271,7 @@ nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath, if ( !*outPath ) { [pool release]; + [keyWindow makeKeyAndOrderFront:nil]; return NFD_ERROR; } memcpy( *outPath, utf8Path, len+1 ); /* copy null term */ diff --git a/Engine/lib/nativeFileDialogs/nfd_gtk.c b/Engine/lib/nativeFileDialogs/nfd_gtk.c index 65bc41dad..7a9958ed1 100644 --- a/Engine/lib/nativeFileDialogs/nfd_gtk.c +++ b/Engine/lib/nativeFileDialogs/nfd_gtk.c @@ -165,7 +165,7 @@ static void WaitForCleanup(void) /* public */ -nfdresult_t NFD_OpenDialog( const char *filterList, +nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList, const nfdchar_t *defaultPath, nfdchar_t **outPath ) { diff --git a/Engine/lib/nativeFileDialogs/nfd_win.cpp b/Engine/lib/nativeFileDialogs/nfd_win.cpp index 1187fc84b..9e94d9d58 100644 --- a/Engine/lib/nativeFileDialogs/nfd_win.cpp +++ b/Engine/lib/nativeFileDialogs/nfd_win.cpp @@ -4,6 +4,10 @@ http://www.frogtoss.com/labs */ +#define _CRTDBG_MAP_ALLOC +#include +#include + /* only locally define UNICODE in this compilation unit */ #ifndef UNICODE #define UNICODE @@ -19,7 +23,7 @@ #include #include #include -#include +#include #include "nfd_common.h" @@ -47,9 +51,9 @@ static void CopyWCharToNFDChar( const wchar_t *inStr, nfdchar_t **outStr ) /* includes NULL terminator byte in return */ static size_t GetUTF8ByteCountForWChar( const wchar_t *str ) { - int bytesNeeded = WideCharToMultiByte( CP_UTF8, 0, - str, -1, - NULL, 0, NULL, NULL ); + size_t bytesNeeded = WideCharToMultiByte( CP_UTF8, 0, + str, -1, + NULL, 0, NULL, NULL ); assert( bytesNeeded ); return bytesNeeded+1; } @@ -148,12 +152,12 @@ static nfdresult_t AddFiltersToDialog( ::IFileDialog *fileOpenDialog, const char } /* filterCount plus 1 because we hardcode the *.* wildcard after the while loop */ - COMDLG_FILTERSPEC *specList = (COMDLG_FILTERSPEC*)NFDi_Malloc( sizeof(COMDLG_FILTERSPEC) * (filterCount + 1) ); + COMDLG_FILTERSPEC *specList = (COMDLG_FILTERSPEC*)NFDi_Malloc( sizeof(COMDLG_FILTERSPEC) * ((size_t)filterCount + 1) ); if ( !specList ) { return NFD_ERROR; } - for (size_t i = 0; i < filterCount+1; ++i ) + for (UINT i = 0; i < filterCount+1; ++i ) { specList[i].pszName = NULL; specList[i].pszSpec = NULL; @@ -181,9 +185,8 @@ static nfdresult_t AddFiltersToDialog( ::IFileDialog *fileOpenDialog, const char if ( *p_filterList == ';' || *p_filterList == '\0' ) { /* end of filter -- add it to specList */ - - // Empty filter name -- Windows describes them by extension. - CopyNFDCharToWChar(specbuf, (wchar_t**)&specList[specIdx].pszName); + + CopyNFDCharToWChar( specbuf, (wchar_t**)&specList[specIdx].pszName ); CopyNFDCharToWChar( specbuf, (wchar_t**)&specList[specIdx].pszSpec ); memset( specbuf, 0, sizeof(char)*NFD_MAX_STRLEN ); @@ -270,6 +273,8 @@ static nfdresult_t AllocPathSet( IShellItemArray *shellItems, nfdpathset_t *path // Calculate length of name with UTF-8 encoding bufSize += GetUTF8ByteCountForWChar( name ); + + CoTaskMemFree(name); } assert(bufSize); @@ -304,6 +309,7 @@ static nfdresult_t AllocPathSet( IShellItemArray *shellItems, nfdpathset_t *path shellItem->GetDisplayName(SIGDN_FILESYSPATH, &name); int bytesWritten = CopyWCharToExistingNFDCharBuffer(name, p_buf); + CoTaskMemFree(name); ptrdiff_t index = p_buf - pathSet->buf; assert( index >= 0 ); @@ -353,29 +359,30 @@ static nfdresult_t SetDefaultPath( IFileDialog *dialog, const char *defaultPath /* public */ -nfdresult_t NFD_OpenDialog( const char *filterList, +nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList, const nfdchar_t *defaultPath, nfdchar_t **outPath ) { nfdresult_t nfdResult = NFD_ERROR; // Init COM library. - HRESULT result = ::CoInitializeEx(NULL, - ::COINIT_APARTMENTTHREADED | - ::COINIT_DISABLE_OLE1DDE ); + HRESULT coResult = ::CoInitializeEx(NULL, + ::COINIT_APARTMENTTHREADED | + ::COINIT_DISABLE_OLE1DDE ); ::IFileOpenDialog *fileOpenDialog(NULL); - if ( !SUCCEEDED(result)) + if ( !SUCCEEDED(coResult)) { + fileOpenDialog = NULL; NFDi_SetError("Could not initialize COM."); goto end; } // Create dialog - result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL, - CLSCTX_ALL, ::IID_IFileOpenDialog, - reinterpret_cast(&fileOpenDialog) ); + HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL, + CLSCTX_ALL, ::IID_IFileOpenDialog, + reinterpret_cast(&fileOpenDialog) ); if ( !SUCCEEDED(result) ) { @@ -412,6 +419,7 @@ nfdresult_t NFD_OpenDialog( const char *filterList, if ( !SUCCEEDED(result) ) { NFDi_SetError("Could not get file path for selected."); + shellItem->Release(); goto end; } @@ -420,6 +428,7 @@ nfdresult_t NFD_OpenDialog( const char *filterList, if ( !*outPath ) { /* error is malloc-based, error message would be redundant */ + shellItem->Release(); goto end; } @@ -436,8 +445,12 @@ nfdresult_t NFD_OpenDialog( const char *filterList, nfdResult = NFD_ERROR; } - end: - ::CoUninitialize(); +end: + if (fileOpenDialog) + fileOpenDialog->Release(); + + if (SUCCEEDED(coResult)) + ::CoUninitialize(); return nfdResult; } @@ -449,10 +462,10 @@ nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList, nfdresult_t nfdResult = NFD_ERROR; // Init COM library. - HRESULT result = ::CoInitializeEx(NULL, - ::COINIT_APARTMENTTHREADED | - ::COINIT_DISABLE_OLE1DDE ); - if ( !SUCCEEDED(result)) + HRESULT coResult = ::CoInitializeEx(NULL, + ::COINIT_APARTMENTTHREADED | + ::COINIT_DISABLE_OLE1DDE ); + if ( !SUCCEEDED(coResult)) { NFDi_SetError("Could not initialize COM."); return NFD_ERROR; @@ -461,12 +474,13 @@ nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList, ::IFileOpenDialog *fileOpenDialog(NULL); // Create dialog - result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL, - CLSCTX_ALL, ::IID_IFileOpenDialog, - reinterpret_cast(&fileOpenDialog) ); + HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL, + CLSCTX_ALL, ::IID_IFileOpenDialog, + reinterpret_cast(&fileOpenDialog) ); if ( !SUCCEEDED(result) ) { + fileOpenDialog = NULL; NFDi_SetError("Could not create dialog."); goto end; } @@ -512,6 +526,7 @@ nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList, if ( AllocPathSet( shellItems, outPaths ) == NFD_ERROR ) { + shellItems->Release(); goto end; } @@ -528,8 +543,12 @@ nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList, nfdResult = NFD_ERROR; } - end: - ::CoUninitialize(); +end: + if ( fileOpenDialog ) + fileOpenDialog->Release(); + + if (SUCCEEDED(coResult)) + ::CoUninitialize(); return nfdResult; } @@ -541,10 +560,10 @@ nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList, nfdresult_t nfdResult = NFD_ERROR; // Init COM library. - HRESULT result = ::CoInitializeEx(NULL, - ::COINIT_APARTMENTTHREADED | - ::COINIT_DISABLE_OLE1DDE ); - if ( !SUCCEEDED(result)) + HRESULT coResult = ::CoInitializeEx(NULL, + ::COINIT_APARTMENTTHREADED | + ::COINIT_DISABLE_OLE1DDE ); + if ( !SUCCEEDED(coResult)) { NFDi_SetError("Could not initialize COM."); return NFD_ERROR; @@ -553,12 +572,13 @@ nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList, ::IFileSaveDialog *fileSaveDialog(NULL); // Create dialog - result = ::CoCreateInstance(::CLSID_FileSaveDialog, NULL, - CLSCTX_ALL, ::IID_IFileSaveDialog, - reinterpret_cast(&fileSaveDialog) ); + HRESULT result = ::CoCreateInstance(::CLSID_FileSaveDialog, NULL, + CLSCTX_ALL, ::IID_IFileSaveDialog, + reinterpret_cast(&fileSaveDialog) ); if ( !SUCCEEDED(result) ) { + fileSaveDialog = NULL; NFDi_SetError("Could not create dialog."); goto end; } @@ -591,6 +611,7 @@ nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList, result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath); if ( !SUCCEEDED(result) ) { + shellItem->Release(); NFDi_SetError("Could not get file path for selected."); goto end; } @@ -600,6 +621,7 @@ nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList, if ( !*outPath ) { /* error is malloc-based, error message would be redundant */ + shellItem->Release(); goto end; } @@ -616,8 +638,12 @@ nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList, nfdResult = NFD_ERROR; } - end: - ::CoUninitialize(); +end: + if ( fileSaveDialog ) + fileSaveDialog->Release(); + + if (SUCCEEDED(coResult)) + ::CoUninitialize(); return nfdResult; } @@ -729,7 +755,8 @@ nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath, ComPtr pShellItem; if (!SUCCEEDED(pFileDialog->GetResult(&pShellItem))) { - return NFD_OKAY; + NFDi_SetError("Could not get shell item from dialog."); + return NFD_ERROR; } // Finally get the path diff --git a/Engine/lib/nativeFileDialogs/nfd_zenity.c b/Engine/lib/nativeFileDialogs/nfd_zenity.c new file mode 100644 index 000000000..1890eec9e --- /dev/null +++ b/Engine/lib/nativeFileDialogs/nfd_zenity.c @@ -0,0 +1,311 @@ +/* + Native File Dialog + + http://www.frogtoss.com/labs +*/ + +#include +#include +#include +#include "nfd.h" +#include "nfd_common.h" + +#define SIMPLE_EXEC_IMPLEMENTATION +#include "simple_exec.h" + + +const char NO_ZENITY_MSG[] = "zenity not installed"; + + +static void AddTypeToFilterName( const char *typebuf, char *filterName, size_t bufsize ) +{ + size_t len = strlen(filterName); + if( len > 0 ) + strncat( filterName, " *.", bufsize - len - 1 ); + else + strncat( filterName, "--file-filter=*.", bufsize - len - 1 ); + + len = strlen(filterName); + strncat( filterName, typebuf, bufsize - len - 1 ); +} + +static void AddFiltersToCommandArgs(char** commandArgs, int commandArgsLen, const char *filterList ) +{ + char typebuf[NFD_MAX_STRLEN] = {0}; + const char *p_filterList = filterList; + char *p_typebuf = typebuf; + char filterName[NFD_MAX_STRLEN] = {0}; + int i; + + if ( !filterList || strlen(filterList) == 0 ) + return; + + while ( 1 ) + { + + if ( NFDi_IsFilterSegmentChar(*p_filterList) ) + { + char typebufWildcard[NFD_MAX_STRLEN]; + /* add another type to the filter */ + assert( strlen(typebuf) > 0 ); + assert( strlen(typebuf) < NFD_MAX_STRLEN-1 ); + + snprintf( typebufWildcard, NFD_MAX_STRLEN, "*.%s", typebuf ); + + AddTypeToFilterName( typebuf, filterName, NFD_MAX_STRLEN ); + + p_typebuf = typebuf; + memset( typebuf, 0, sizeof(char) * NFD_MAX_STRLEN ); + } + + if ( *p_filterList == ';' || *p_filterList == '\0' ) + { + /* end of filter -- add it to the dialog */ + + for(i = 0; commandArgs[i] != NULL && i < commandArgsLen; i++); + + commandArgs[i] = strdup(filterName); + + filterName[0] = '\0'; + + if ( *p_filterList == '\0' ) + break; + } + + if ( !NFDi_IsFilterSegmentChar( *p_filterList ) ) + { + *p_typebuf = *p_filterList; + p_typebuf++; + } + + p_filterList++; + } + + /* always append a wildcard option to the end*/ + + for(i = 0; commandArgs[i] != NULL && i < commandArgsLen; i++); + + commandArgs[i] = strdup("--file-filter=*.*"); +} + +static nfdresult_t ZenityCommon(char** command, int commandLen, const char* defaultPath, const char* filterList, char** stdOut) +{ + if(defaultPath != NULL) + { + char* prefix = "--filename"; + int len = strlen(prefix) + strlen(defaultPath) + 1; + + char* tmp = (char*) calloc(len, 1); + strcat(tmp, prefix); + strcat(tmp, defaultPath); + + int i; + for(i = 0; command[i] != NULL && i < commandLen; i++); + + command[i] = tmp; + } + + AddFiltersToCommandArgs(command, commandLen, filterList); + + int byteCount = 0; + int exitCode = 0; + int processInvokeError = runCommandArray(stdOut, &byteCount, &exitCode, 0, command); + + for(int i = 0; command[i] != NULL && i < commandLen; i++) + free(command[i]); + + nfdresult_t result = NFD_OKAY; + + if(processInvokeError == COMMAND_NOT_FOUND) + { + NFDi_SetError(NO_ZENITY_MSG); + result = NFD_ERROR; + } + else + { + if(exitCode == 1) + result = NFD_CANCEL; + } + + return result; +} + + +static nfdresult_t AllocPathSet(char* zenityList, nfdpathset_t *pathSet ) +{ + size_t bufSize = 0; + nfdchar_t *p_buf; + size_t count = 0; + + assert(zenityList); + assert(pathSet); + + size_t len = strlen(zenityList) + 1; + pathSet->buf = NFDi_Malloc(len); + + int numEntries = 1; + + for(size_t i = 0; i < len; i++) + { + char ch = zenityList[i]; + + if(ch == '|') + { + numEntries++; + ch = '\0'; + } + + pathSet->buf[i] = ch; + } + + pathSet->count = numEntries; + assert( pathSet->count > 0 ); + + pathSet->indices = NFDi_Malloc( sizeof(size_t)*pathSet->count ); + + int entry = 0; + pathSet->indices[0] = 0; + for(size_t i = 0; i < len; i++) + { + char ch = zenityList[i]; + + if(ch == '|') + { + entry++; + pathSet->indices[entry] = i + 1; + } + } + + return NFD_OKAY; +} + +/* public */ + +nfdresult_t NFD_OpenDialog( const char *filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath ) +{ + int commandLen = 100; + char* command[commandLen]; + memset(command, 0, commandLen * sizeof(char*)); + + command[0] = strdup("zenity"); + command[1] = strdup("--file-selection"); + command[2] = strdup("--title=Open File"); + + char* stdOut = NULL; + nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut); + + if(stdOut != NULL) + { + size_t len = strlen(stdOut); + *outPath = NFDi_Malloc(len); + memcpy(*outPath, stdOut, len); + (*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator + free(stdOut); + } + else + { + *outPath = NULL; + } + + return result; +} + + +nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList, + const nfdchar_t *defaultPath, + nfdpathset_t *outPaths ) +{ + int commandLen = 100; + char* command[commandLen]; + memset(command, 0, commandLen * sizeof(char*)); + + command[0] = strdup("zenity"); + command[1] = strdup("--file-selection"); + command[2] = strdup("--title=Open Files"); + command[3] = strdup("--multiple"); + + char* stdOut = NULL; + nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut); + + if(stdOut != NULL) + { + size_t len = strlen(stdOut); + stdOut[len-1] = '\0'; // remove trailing newline + + if ( AllocPathSet( stdOut, outPaths ) == NFD_ERROR ) + result = NFD_ERROR; + + free(stdOut); + } + else + { + result = NFD_ERROR; + } + + return result; +} + +nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath ) +{ + int commandLen = 100; + char* command[commandLen]; + memset(command, 0, commandLen * sizeof(char*)); + + command[0] = strdup("zenity"); + command[1] = strdup("--file-selection"); + command[2] = strdup("--title=Save File"); + command[3] = strdup("--save"); + + char* stdOut = NULL; + nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut); + + if(stdOut != NULL) + { + size_t len = strlen(stdOut); + *outPath = NFDi_Malloc(len); + memcpy(*outPath, stdOut, len); + (*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator + free(stdOut); + } + else + { + *outPath = NULL; + } + + return result; +} + +nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath, + nfdchar_t **outPath) +{ + int commandLen = 100; + char* command[commandLen]; + memset(command, 0, commandLen * sizeof(char*)); + + command[0] = strdup("zenity"); + command[1] = strdup("--file-selection"); + command[2] = strdup("--directory"); + command[3] = strdup("--title=Select folder"); + + char* stdOut = NULL; + nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, "", &stdOut); + + if(stdOut != NULL) + { + size_t len = strlen(stdOut); + *outPath = NFDi_Malloc(len); + memcpy(*outPath, stdOut, len); + (*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator + free(stdOut); + } + else + { + *outPath = NULL; + } + + return result; +} diff --git a/Engine/lib/nativeFileDialogs/simple_exec.h b/Engine/lib/nativeFileDialogs/simple_exec.h new file mode 100644 index 000000000..b85327b55 --- /dev/null +++ b/Engine/lib/nativeFileDialogs/simple_exec.h @@ -0,0 +1,214 @@ +// copied from: https://github.com/wheybags/simple_exec/blob/5a74c507c4ce1b2bb166177ead4cca7cfa23cb35/simple_exec.h + +// simple_exec.h, single header library to run external programs + retrieve their status code and output (unix only for now) +// +// do this: +// #define SIMPLE_EXEC_IMPLEMENTATION +// before you include this file in *one* C or C++ file to create the implementation. +// i.e. it should look like this: +// #define SIMPLE_EXEC_IMPLEMENTATION +// #include "simple_exec.h" + +#ifndef SIMPLE_EXEC_H +#define SIMPLE_EXEC_H + +int runCommand(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* command, ...); +int runCommandArray(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* const* allArgs); + +#endif // SIMPLE_EXEC_H + +#ifdef SIMPLE_EXEC_IMPLEMENTATION + +#include +#include +#include +#include +#include +#include +#include +#include + +#define release_assert(x) do { int __release_assert_tmp__ = (x); assert(__release_assert_tmp__); } while(0) + +enum PIPE_FILE_DESCRIPTORS +{ + READ_FD = 0, + WRITE_FD = 1 +}; + +enum RUN_COMMAND_ERROR +{ + COMMAND_RAN_OK = 0, + COMMAND_NOT_FOUND = 1 +}; + +int runCommandArray(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* const* allArgs) +{ + // adapted from: https://stackoverflow.com/a/479103 + + int bufferSize = 256; + char buffer[bufferSize + 1]; + + int dataReadFromChildDefaultSize = bufferSize * 5; + int dataReadFromChildSize = dataReadFromChildDefaultSize; + int dataReadFromChildUsed = 0; + char* dataReadFromChild = (char*)malloc(dataReadFromChildSize); + + + int parentToChild[2]; + release_assert(pipe(parentToChild) == 0); + + int childToParent[2]; + release_assert(pipe(childToParent) == 0); + + int errPipe[2]; + release_assert(pipe(errPipe) == 0); + + pid_t pid; + switch( pid = fork() ) + { + case -1: + { + release_assert(0 && "Fork failed"); + } + + case 0: // child + { + release_assert(dup2(parentToChild[READ_FD ], STDIN_FILENO ) != -1); + release_assert(dup2(childToParent[WRITE_FD], STDOUT_FILENO) != -1); + + if(includeStdErr) + { + release_assert(dup2(childToParent[WRITE_FD], STDERR_FILENO) != -1); + } + else + { + int devNull = open("/dev/null", O_WRONLY); + release_assert(dup2(devNull, STDERR_FILENO) != -1); + } + + // unused + release_assert(close(parentToChild[WRITE_FD]) == 0); + release_assert(close(childToParent[READ_FD ]) == 0); + release_assert(close(errPipe[READ_FD]) == 0); + + const char* command = allArgs[0]; + execvp(command, allArgs); + + char err = 1; + write(errPipe[WRITE_FD], &err, 1); + + close(errPipe[WRITE_FD]); + close(parentToChild[READ_FD]); + close(childToParent[WRITE_FD]); + + exit(0); + } + + + default: // parent + { + // unused + release_assert(close(parentToChild[READ_FD]) == 0); + release_assert(close(childToParent[WRITE_FD]) == 0); + release_assert(close(errPipe[WRITE_FD]) == 0); + + while(1) + { + ssize_t bytesRead = 0; + switch(bytesRead = read(childToParent[READ_FD], buffer, bufferSize)) + { + case 0: // End-of-File, or non-blocking read. + { + int status = 0; + release_assert(waitpid(pid, &status, 0) == pid); + + // done with these now + release_assert(close(parentToChild[WRITE_FD]) == 0); + release_assert(close(childToParent[READ_FD]) == 0); + + char errChar = 0; + read(errPipe[READ_FD], &errChar, 1); + close(errPipe[READ_FD]); + + if(errChar) + { + free(dataReadFromChild); + return COMMAND_NOT_FOUND; + } + + // free any un-needed memory with realloc + add a null terminator for convenience + dataReadFromChild = (char*)realloc(dataReadFromChild, dataReadFromChildUsed + 1); + dataReadFromChild[dataReadFromChildUsed] = '\0'; + + if(stdOut != NULL) + *stdOut = dataReadFromChild; + else + free(dataReadFromChild); + + if(stdOutByteCount != NULL) + *stdOutByteCount = dataReadFromChildUsed; + if(returnCode != NULL) + *returnCode = WEXITSTATUS(status); + + return COMMAND_RAN_OK; + } + case -1: + { + release_assert(0 && "read() failed"); + } + + default: + { + if(dataReadFromChildUsed + bytesRead + 1 >= dataReadFromChildSize) + { + dataReadFromChildSize += dataReadFromChildDefaultSize; + dataReadFromChild = (char*)realloc(dataReadFromChild, dataReadFromChildSize); + } + + memcpy(dataReadFromChild + dataReadFromChildUsed, buffer, bytesRead); + dataReadFromChildUsed += bytesRead; + break; + } + } + } + } + } +} + +int runCommand(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* command, ...) +{ + va_list vl; + va_start(vl, command); + + char* currArg = NULL; + + int allArgsInitialSize = 16; + int allArgsSize = allArgsInitialSize; + char** allArgs = (char**)malloc(sizeof(char*) * allArgsSize); + allArgs[0] = command; + + int i = 1; + do + { + currArg = va_arg(vl, char*); + allArgs[i] = currArg; + + i++; + + if(i >= allArgsSize) + { + allArgsSize += allArgsInitialSize; + allArgs = (char**)realloc(allArgs, sizeof(char*) * allArgsSize); + } + + } while(currArg != NULL); + + va_end(vl); + + int retval = runCommandArray(stdOut, stdOutByteCount, returnCode, includeStdErr, allArgs); + free(allArgs); + return retval; +} + +#endif //SIMPLE_EXEC_IMPLEMENTATION From 989662774e16964c01d56c5bfbd7df0fffa7a78f Mon Sep 17 00:00:00 2001 From: Azaezel Date: Tue, 1 Jan 2019 11:00:05 -0600 Subject: [PATCH 2/6] blacklist updated to filter out the new zenity backend when on windows. likely need to do the same on mac and potentially resolve conflicts on linux --- Tools/CMake/torque3d.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/CMake/torque3d.cmake b/Tools/CMake/torque3d.cmake index 751c908d8..ec3eb110e 100644 --- a/Tools/CMake/torque3d.cmake +++ b/Tools/CMake/torque3d.cmake @@ -470,7 +470,7 @@ if(TORQUE_SDL) addLib(nativeFileDialogs) set(BLACKLIST "" ) else() - set(BLACKLIST "nfd_gtk.c" "nfd_cocoa.m" ) + set(BLACKLIST "nfd_gtk.c" "nfd_cocoa.m" "simple_exec.h" "nfd_zenity.c") addLib(nativeFileDialogs) set(BLACKLIST "" ) addLib(comctl32) From 8304168365138c4d70d1f2a0c0ae1accfa945554 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Tue, 1 Jan 2019 15:40:44 -0600 Subject: [PATCH 3/6] linux compilation, defaults to using the older gtk solution till more folks have a chance to beat up the zenity one. --- Tools/CMake/torque3d.cmake | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Tools/CMake/torque3d.cmake b/Tools/CMake/torque3d.cmake index ec3eb110e..0bb20f6f1 100644 --- a/Tools/CMake/torque3d.cmake +++ b/Tools/CMake/torque3d.cmake @@ -204,6 +204,9 @@ mark_as_advanced(TORQUE_DEBUG_GFX_MODE) #option(DEBUG_SPEW "more debug" OFF) set(TORQUE_NO_DSO_GENERATION ON) +option(TORQUE_UseZenity "use the Zenity backend for NFD" OFF) +mark_as_advanced(TORQUE_UseZenity) + if(WIN32) # warning C4800: 'XXX' : forcing value to bool 'true' or 'false' (performance warning) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4800") @@ -460,7 +463,11 @@ if(TORQUE_SDL) # Add other flags to the compiler add_definitions(${GTK3_CFLAGS_OTHER}) - set(BLACKLIST "nfd_win.cpp" "nfd_cocoa.m" ) + if(TORQUE_UseZenity) + set(BLACKLIST "nfd_win.cpp" "nfd_cocoa.m" ) + else() + set(BLACKLIST "nfd_win.cpp" "nfd_cocoa.m" "simple_exec.h" "nfd_zenity.c") + endif() addLib(nativeFileDialogs) set(BLACKLIST "" ) From 95eb2fc626bc7b64b056b5ffa788614d635d70d1 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Sun, 6 Jan 2019 14:24:33 -0600 Subject: [PATCH 4/6] zenity backend file filter fix --- Tools/CMake/torque3d.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/CMake/torque3d.cmake b/Tools/CMake/torque3d.cmake index 0bb20f6f1..37f1f4b52 100644 --- a/Tools/CMake/torque3d.cmake +++ b/Tools/CMake/torque3d.cmake @@ -464,7 +464,7 @@ if(TORQUE_SDL) add_definitions(${GTK3_CFLAGS_OTHER}) if(TORQUE_UseZenity) - set(BLACKLIST "nfd_win.cpp" "nfd_cocoa.m" ) + set(BLACKLIST "nfd_win.cpp" "nfd_cocoa.m" "nfd_gtk.c" ) else() set(BLACKLIST "nfd_win.cpp" "nfd_cocoa.m" "simple_exec.h" "nfd_zenity.c") endif() From 831e9db5a57e29c1fc2dce1c589de8e8ef1cea66 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Sun, 6 Jan 2019 15:42:18 -0600 Subject: [PATCH 5/6] constant consistency creates continuums --- Tools/CMake/torque3d.cmake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tools/CMake/torque3d.cmake b/Tools/CMake/torque3d.cmake index 37f1f4b52..53d503c50 100644 --- a/Tools/CMake/torque3d.cmake +++ b/Tools/CMake/torque3d.cmake @@ -204,8 +204,8 @@ mark_as_advanced(TORQUE_DEBUG_GFX_MODE) #option(DEBUG_SPEW "more debug" OFF) set(TORQUE_NO_DSO_GENERATION ON) -option(TORQUE_UseZenity "use the Zenity backend for NFD" OFF) -mark_as_advanced(TORQUE_UseZenity) +option(TORQUE_USE_ZENITY "use the Zenity backend for NFD" OFF) +mark_as_advanced(TORQUE_USE_ZENITY) if(WIN32) # warning C4800: 'XXX' : forcing value to bool 'true' or 'false' (performance warning) @@ -463,7 +463,7 @@ if(TORQUE_SDL) # Add other flags to the compiler add_definitions(${GTK3_CFLAGS_OTHER}) - if(TORQUE_UseZenity) + if(TORQUE_USE_ZENITY) set(BLACKLIST "nfd_win.cpp" "nfd_cocoa.m" "nfd_gtk.c" ) else() set(BLACKLIST "nfd_win.cpp" "nfd_cocoa.m" "simple_exec.h" "nfd_zenity.c") From aebbb1e3d8790c2f1f6edfea17d6102e97754e3f Mon Sep 17 00:00:00 2001 From: Azaezel Date: Sun, 6 Jan 2019 15:50:19 -0600 Subject: [PATCH 6/6] tabspace --- Tools/CMake/torque3d.cmake | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tools/CMake/torque3d.cmake b/Tools/CMake/torque3d.cmake index 53d503c50..d827663f5 100644 --- a/Tools/CMake/torque3d.cmake +++ b/Tools/CMake/torque3d.cmake @@ -463,11 +463,11 @@ if(TORQUE_SDL) # Add other flags to the compiler add_definitions(${GTK3_CFLAGS_OTHER}) - if(TORQUE_USE_ZENITY) - set(BLACKLIST "nfd_win.cpp" "nfd_cocoa.m" "nfd_gtk.c" ) - else() - set(BLACKLIST "nfd_win.cpp" "nfd_cocoa.m" "simple_exec.h" "nfd_zenity.c") - endif() + if(TORQUE_USE_ZENITY) + set(BLACKLIST "nfd_win.cpp" "nfd_cocoa.m" "nfd_gtk.c" ) + else() + set(BLACKLIST "nfd_win.cpp" "nfd_cocoa.m" "simple_exec.h" "nfd_zenity.c") + endif() addLib(nativeFileDialogs) set(BLACKLIST "" )