diff --git a/README.md b/README.md index 9ad6039..9b068f7 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Features: - Support for non-deprecated Cocoa APIs on OS X. - GTK3 dialog on Linux. - Optional Zenity support on Linux to avoid linking GTK. + - Haiku support - Tested, works alongside [http://www.libsdl.org](SDL2) on all platforms, for the game developers out there. # Example Usage # @@ -54,6 +55,7 @@ See self-documenting API [NFD.h](src/include/nfd.h) for more options. ![Windows rendering a dialog](screens/open_win.png?raw=true) ![GTK3 on Linux rendering a dialog](screens/open_gtk3.png?raw=true) ![Cocoa on MacOS rendering a dialog](screens/open_cocoa.png?raw=true) +![Haiku](screens/open_haiku.png?raw=true) ## Changelog ## @@ -155,6 +157,9 @@ On Mac OS, add `AppKit` to the list of frameworks. On Windows, ensure you are linking against `comctl32.lib`. +#### Haiku #### +On Haiku, you need to link to `libtracker` and `libbe`. + ## Usage ## See `NFD.h` for API calls. See `tests/*.c` for example code. @@ -195,6 +200,7 @@ I accept quality code patches, or will resolve these and other matters through s - It errors out if Zenity is not installed on the user's system - This backend's process exec error handling does not gracefully handle numerous error cases, choosing to abort rather than cleanup and return. - Unlike the other backends, the Zenity backend does not return implied extensions from filterlists. [#95](https://github.com/mlabbe/nativefiledialog/issues/95 "Issue 95") + - On Haiku, the file open dialog either shows only allowed files or all files, which is chosen at build time. # Copyright and Credit # @@ -210,6 +216,8 @@ Tomasz Konojacki for [microutf8](http://puszcza.gnu.org.ua/software/microutf8/) [Tom Mason](https://github.com/wheybags) for Zenity support. +[Puck Meerburg](https://github.com/puckipedia/nativefiledialog) for Haiku support. + Various pull requests and bugfixes -- thanks to the original authors. ## Support ## diff --git a/build/premake5.lua b/build/premake5.lua index fb525ab..6c75842 100644 --- a/build/premake5.lua +++ b/build/premake5.lua @@ -53,6 +53,8 @@ workspace "NativeFileDialog" platforms {"x64", "x86"} filter "system:linux or system:macosx" platforms {"arm64"} + filter "system:haiku" + platforms {"x86", "x64"} objdir(path.join(build_dir, "obj/")) @@ -105,6 +107,9 @@ workspace "NativeFileDialog" language "C" files {root_dir.."src/nfd_cocoa.m"} + filter "system:haiku" + language "C++" + files {root_dir.."src/nfd_haiku.cpp"} filter {"system:linux", "options:linux_backend=gtk3"} @@ -174,6 +179,10 @@ local make_test = function(name) filter {"system:macosx"} links {"Foundation.framework", "AppKit.framework"} + filter {"system:haiku"} + -- should link to stdc++.r4 for gcc2 + links {"be", "tracker", "stdc++"} + filter {"configurations:Debug", "system:linux", "options:linux_backend=gtk3"} linkoptions {"-lnfd_d `pkg-config --libs gtk+-3.0`"} filter {"configurations:Debug", "system:linux", "options:linux_backend=zenity"} @@ -231,6 +240,7 @@ newaction premake_do_action("gmake", "linux", true,{}) premake_do_action("gmake", "linux", true,{linux_backend='zenity'}) premake_do_action("gmake", "macosx", true,{}) + premake_do_action("gmake", "haiku", true,{}) premake_do_action("gmake", "windows", true,{}) end } @@ -276,6 +286,7 @@ newaction "xcode4", "gmake_linux", "gmake_macosx", + "gmake_haiku", "gmake_windows" } diff --git a/screens/open_haiku.png b/screens/open_haiku.png new file mode 100644 index 0000000..da880a4 Binary files /dev/null and b/screens/open_haiku.png differ diff --git a/src/ftg_core.h b/src/ftg_core.h index 857f03a..53d8709 100644 --- a/src/ftg_core.h +++ b/src/ftg_core.h @@ -114,7 +114,8 @@ /* broad test for OSes that are vaguely POSIX compliant */ #if defined(__linux__) || defined(__APPLE__) || defined(ANDROID) || \ defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) || \ - defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__) + defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__) || \ + defined(__HAIKU__) # define FTG_POSIX_LIKE 1 #endif @@ -399,7 +400,7 @@ typedef struct ftg_dirhandle_s ftg_dirhandle_t; #elif defined(_WIN32) typedef int64_t ftg_off_t; typedef wchar_t ftg_wchar_t; -#elif defined(__FreeBSD__) || defined(__OpenBSD__) +#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__HAIKU__) typedef off_t ftg_off_t; #elif defined(FTG_WASM) typedef off_t ftg_off_t; diff --git a/src/nfd_haiku.cpp b/src/nfd_haiku.cpp new file mode 100644 index 0000000..e8fa7be --- /dev/null +++ b/src/nfd_haiku.cpp @@ -0,0 +1,425 @@ +/* + Native File Dialog + + http://www.frogtoss.com/labs + */ + + +#include "nfd.h" +#include "nfd_common.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +const int32 kOpenResponse = 'oREF'; +const int32 kSaveResponse = 'sREF'; +const int32 kCancelResponse = 'cREF'; + +class ExtensionRefFilter : public BRefFilter { +public: + ExtensionRefFilter(BString stuff); + bool Filter(const entry_ref* ref, BNode* node, + struct stat_beos* stat, const char* mimeType); +private: + BStringList mExtensions; +}; + +#define FILTER 1 + +ExtensionRefFilter::ExtensionRefFilter(BString stuff) { + int32 start = 0; + while(start < stuff.Length()) { + int32 comma = stuff.FindFirst(",", start); + int32 semicolon = stuff.FindFirst(";", start); + if (comma >= 0 && (comma <= semicolon || semicolon < 0)) { + mExtensions.Add(BString(stuff.String() + start, comma - start)); + start = comma + 1; + } else if (semicolon >= 0 && (semicolon < comma || comma < 0)) { + mExtensions.Add(BString(stuff.String() + start, semicolon - start)); + start = semicolon + 1; + } else { + if (stuff.Length() - start < 1) + break; + mExtensions.Add(BString(stuff.String() + start)); + break; + } + } + + for (int32 i = 0; i < mExtensions.CountStrings(); i++) { + mExtensions.StringAt(i).Prepend("."); + } +} + + +bool +ExtensionRefFilter::Filter(const entry_ref* ref, BNode* node, + struct stat_beos* stat, const char* mimeType) +{ + #if !FILTER + return true; + #endif + if (S_ISDIR(stat->st_mode)) + return true; + + BString name(ref->name); + for(int32 i = 0; i < mExtensions.CountStrings(); i++) { + if (name.EndsWith(mExtensions.StringAt(i))) + return true; + } + + return false; +} + + +struct response_data { + struct { + int32 count; + entry_ref *refs; + } open; + + struct { + entry_ref directory; + BString filename; + } save; +}; + + +class DialogHandler : public BLooper { +public: + DialogHandler(); + ~DialogHandler() { delete_sem(mSemaphore); } + void MessageReceived(BMessage *msg); + int32 ResponseId() { return mResponseId; } + response_data &ResponseData() { return mResponseData; } + void Wait() { acquire_sem(mSemaphore); } +private: + sem_id mSemaphore; + int32 mResponseId; + response_data mResponseData; +}; + + +DialogHandler::DialogHandler() + : BLooper(), + mResponseId(0) +{ + mSemaphore = create_sem(0, "NativeFileDialog helper"); +} + +void +DialogHandler::MessageReceived(BMessage *msg) +{ + if (mResponseId != 0) { + BLooper::MessageReceived(msg); + return; + } + + switch(msg->what) { + case B_REFS_RECEIVED: { + mResponseId = kOpenResponse; + msg->GetInfo("refs", NULL, &mResponseData.open.count); + mResponseData.open.refs = new entry_ref[mResponseData.open.count]; + for (int32 i = 0; i < mResponseData.open.count; i++) { + msg->FindRef("refs", i, &mResponseData.open.refs[i]); + } + break; + } + + case B_SAVE_REQUESTED: { + mResponseId = kSaveResponse; + msg->FindRef("directory", &mResponseData.save.directory); + msg->FindString("name", &mResponseData.save.filename); + break; + } + + case B_CANCEL: { + mResponseId = kCancelResponse; + break; + } + + default: + BLooper::MessageReceived(msg); + } + + release_sem(mSemaphore); +} + + +/* single file open dialog */ +nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath ) +{ + BApplication *temporaryApp = NULL; + if (be_app == NULL) { + temporaryApp = new BApplication("application/x-vnd.nfd-dialog"); + } + + DialogHandler *handler = new DialogHandler(); + BMessenger messenger(handler); + BFilePanel *panel = new BFilePanel(B_OPEN_PANEL, NULL, NULL, B_FILE_NODE, false, NULL, NULL, true); + ExtensionRefFilter *filter = NULL; + + if (filterList != NULL && *filterList != 0) { + filter = new ExtensionRefFilter(filterList); + panel->SetRefFilter(filter); + } + + handler->Run(); + panel->SetTarget(messenger); + + if (defaultPath != NULL) { + BEntry directory(defaultPath, true); + panel->SetPanelDirectory(&directory); + } + + panel->Show(); + + handler->Wait(); + + response_data data = handler->ResponseData(); + int32 response = handler->ResponseId(); + handler->PostMessage(B_QUIT_REQUESTED); + + if (temporaryApp) + delete temporaryApp; + + delete panel; + + switch (response) { + case kCancelResponse: + if (filter) + delete filter; + return NFD_CANCEL; + case kOpenResponse: { + if (data.open.count != 1) { + delete[] data.open.refs; + if (filter) + delete filter; + + NFDi_SetError("Got invalid count of refs back"); + return NFD_ERROR; + } + + BPath path(&data.open.refs[0]); + size_t length = NFDi_UTF8_Strlen((nfdchar_t *)path.Path()) + 1; + + *outPath = (nfdchar_t *)NFDi_Malloc(length); + NFDi_SafeStrncpy(*outPath, path.Path(), length); + + if (filter) + delete filter; + delete[] data.open.refs; + return NFD_OKAY; + } + } + + if (filter) + delete filter; + + NFDi_SetError("Got invalid response from port"); + return NFD_ERROR; +} + + +/* multiple file open dialog */ +nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList, + const nfdchar_t *defaultPath, + nfdpathset_t *outPaths ) +{ + BApplication *temporaryApp = NULL; + if (be_app == NULL) { + temporaryApp = new BApplication("application/x-vnd.nfd-dialog"); + } + + DialogHandler *handler = new DialogHandler(); + BMessenger messenger(handler); + BFilePanel *panel = new BFilePanel(B_OPEN_PANEL, NULL, NULL, B_FILE_NODE, false, NULL, NULL, true); + ExtensionRefFilter *filter = NULL; + + if (filterList != NULL && *filterList != 0) { + filter = new ExtensionRefFilter(filterList); + panel->SetRefFilter(filter); + } + + handler->Run(); + panel->SetTarget(messenger); + + if (defaultPath != NULL) { + BEntry directory(defaultPath, true); + panel->SetPanelDirectory(&directory); + } + + panel->Show(); + + handler->Wait(); + + response_data data = handler->ResponseData(); + int32 response = handler->ResponseId(); + + handler->PostMessage(B_QUIT_REQUESTED); + + if (temporaryApp) + delete temporaryApp; + + switch (response) { + case kCancelResponse: + if (filter) + delete filter; + return NFD_CANCEL; + case kOpenResponse: { + size_t total_length = 0; + BPath paths[data.open.count]; + for (int i = 0; i < data.open.count; i++) { + paths[i] = BPath(&data.open.refs[i]); + total_length += NFDi_UTF8_Strlen((nfdchar_t *)paths[i].Path()) + 1; + } + + outPaths->indices = (size_t *)NFDi_Malloc(sizeof(size_t) * data.open.count); + outPaths->count = data.open.count; + outPaths->buf = (nfdchar_t *)NFDi_Malloc(total_length); + + nfdchar_t *buflocation = outPaths->buf; + for (int i = 0; i < data.open.count; i++) { + size_t length = NFDi_UTF8_Strlen((nfdchar_t *)paths[i].Path()) + 1; + NFDi_SafeStrncpy(buflocation, (nfdchar_t *)paths[i].Path(), length); + outPaths->indices[i] = buflocation - outPaths->buf; + buflocation += length; + } + + delete[] data.open.refs; + if (filter) + delete filter; + return NFD_OKAY; + } + } + + if (filter) + delete filter; + + NFDi_SetError("Got invalid response from port"); + return NFD_ERROR; +} + + +/* save dialog */ +nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath ) +{ + BApplication *temporaryApp = NULL; + if (be_app == NULL) { + temporaryApp = new BApplication("application/x-vnd.nfd-dialog"); + } + + DialogHandler *handler = new DialogHandler(); + handler->Run(); + BMessenger messenger(handler); + BFilePanel *panel = new BFilePanel(B_SAVE_PANEL, NULL, NULL, B_FILE_NODE, false, NULL, NULL, true); + panel->SetTarget(messenger); + if (defaultPath != NULL) { + BEntry directory(defaultPath, true); + panel->SetPanelDirectory(&directory); + } + + panel->Show(); + + handler->Wait(); + + response_data data = handler->ResponseData(); + int32 response = handler->ResponseId(); + handler->PostMessage(B_QUIT_REQUESTED); + + if (temporaryApp) + delete temporaryApp; + + switch (response) { + case kCancelResponse: + return NFD_CANCEL; + case kSaveResponse: { + BEntry entry(&data.save.directory); + BPath path(&entry); + size_t dirlength = strlen(path.Path()); + size_t namelength = strlen(data.save.filename); + *outPath = (nfdchar_t *)NFDi_Malloc(dirlength + namelength + 2); + NFDi_SafeStrncpy(*outPath, path.Path(), dirlength + 1); + (*outPath)[dirlength] = '/'; + NFDi_SafeStrncpy((*outPath) + dirlength + 1, data.save.filename, namelength + 1); + return NFD_OKAY; + } + } + + NFDi_SetError("Got invalid response from port"); + return NFD_ERROR; +} + + +/* single folder open dialog */ +nfdresult_t NFD_PickFolder( const nfdchar_t *defaultPath, + nfdchar_t **outPath ) +{ + BApplication *temporaryApp = NULL; + if (be_app == NULL) { + temporaryApp = new BApplication("application/x-vnd.nfd-dialog"); + } + + DialogHandler *handler = new DialogHandler(); + BMessenger messenger(handler); + BFilePanel *panel = new BFilePanel(B_OPEN_PANEL, NULL, NULL, B_DIRECTORY_NODE, false, NULL, NULL, true); + + handler->Run(); + panel->SetTarget(messenger); + + if (defaultPath != NULL) { + BEntry directory(defaultPath, true); + panel->SetPanelDirectory(&directory); + } + + panel->Show(); + + handler->Wait(); + + response_data data = handler->ResponseData(); + int32 response = handler->ResponseId(); + handler->PostMessage(B_QUIT_REQUESTED); + + if (temporaryApp) + delete temporaryApp; + + delete panel; + + switch (response) { + case kCancelResponse: + return NFD_CANCEL; + case kOpenResponse: { + if (data.open.count != 1) { + delete[] data.open.refs; + + NFDi_SetError("Got invalid count of refs back"); + return NFD_ERROR; + } + + BPath path(&data.open.refs[0]); + size_t length = NFDi_UTF8_Strlen((nfdchar_t *)path.Path()) + 1; + + *outPath = (nfdchar_t *)NFDi_Malloc(length); + NFDi_SafeStrncpy(*outPath, path.Path(), length); + + delete[] data.open.refs; + return NFD_OKAY; + } + } + + NFDi_SetError("Got invalid response from port"); + return NFD_ERROR; +}