diff --git a/CMakeLists.txt b/CMakeLists.txt index c0f065c3f..0cd490328 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2873,6 +2873,7 @@ elseif(N3DS) endif() if (SDL_DIALOG) + sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/SDL_dialog_utils.c) if(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/unix/SDL_unixdialog.c) sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/unix/SDL_portaldialog.c) diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj index d68a2f60a..f14f2186a 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj +++ b/VisualC-GDK/SDL/SDL.vcxproj @@ -507,6 +507,7 @@ + diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters index 722b2ed3c..b6747abd1 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -4,6 +4,9 @@ + + dialog + filesystem diff --git a/VisualC-WinRT/SDL-UWP.vcxproj b/VisualC-WinRT/SDL-UWP.vcxproj index ba706bb95..3dbbe892f 100644 --- a/VisualC-WinRT/SDL-UWP.vcxproj +++ b/VisualC-WinRT/SDL-UWP.vcxproj @@ -315,6 +315,7 @@ NotUsing NotUsing + diff --git a/VisualC-WinRT/SDL-UWP.vcxproj.filters b/VisualC-WinRT/SDL-UWP.vcxproj.filters index a8da66c1a..092edfd29 100644 --- a/VisualC-WinRT/SDL-UWP.vcxproj.filters +++ b/VisualC-WinRT/SDL-UWP.vcxproj.filters @@ -31,6 +31,9 @@ {0000012051ca8361c8e1013aee1d0000} + + {0000c99bfadbbcb05a474a8472910000} + @@ -567,6 +570,9 @@ Source Files + + dialog + Source Files diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index 7cc840594..c3143c47c 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -404,6 +404,7 @@ + diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index 77b54b847..66dab4dea 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -196,6 +196,9 @@ {0000d7fda065b13b0ca4ab262c380000} + + {00008dfdfa0190856fbf3c7db52d0000} + @@ -883,6 +886,9 @@ camera + + dialog + filesystem diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj index 4ba76a901..b913cb7f8 100644 --- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -513,6 +513,7 @@ F3FA5A242B59ACE000FEAD97 /* yuv_rgb_lsx.h in Headers */ = {isa = PBXBuildFile; fileRef = F3FA5A1B2B59ACE000FEAD97 /* yuv_rgb_lsx.h */; }; F3FA5A252B59ACE000FEAD97 /* yuv_rgb_common.h in Headers */ = {isa = PBXBuildFile; fileRef = F3FA5A1C2B59ACE000FEAD97 /* yuv_rgb_common.h */; }; FA73671D19A540EF004122E4 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA73671C19A540EF004122E4 /* CoreVideo.framework */; platformFilters = (ios, maccatalyst, macos, tvos, watchos, ); }; + 0000140640E77F73F1DF0000 /* SDL_dialog_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = 0000F6C6A072ED4E3D660000 /* SDL_dialog_utils.c */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1054,6 +1055,7 @@ F59C710600D5CB5801000001 /* SDL.info */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = SDL.info; sourceTree = ""; }; F5A2EF3900C6A39A01000001 /* BUGS.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; name = BUGS.txt; path = ../../BUGS.txt; sourceTree = SOURCE_ROOT; }; FA73671C19A540EF004122E4 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; }; + 0000F6C6A072ED4E3D660000 /* SDL_dialog_utils.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_dialog_utils.c; path = SDL_dialog_utils.c; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -2233,6 +2235,7 @@ children = ( F37E18552BA50ED50098C111 /* cocoa */, F37E18562BA50F2A0098C111 /* dummy */, + 0000F6C6A072ED4E3D660000 /* SDL_dialog_utils.c */, ); path = dialog; sourceTree = ""; @@ -2872,6 +2875,7 @@ 0000481D255AF155B42C0000 /* SDL_sysfsops.c in Sources */, 0000494CC93F3E624D3C0000 /* SDL_systime.c in Sources */, 000095FA1BDE436CF3AF0000 /* SDL_time.c in Sources */, + 0000140640E77F73F1DF0000 /* SDL_dialog_utils.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/include/SDL3/SDL_dialog.h b/include/SDL3/SDL_dialog.h index d4a7577cb..134c8194d 100644 --- a/include/SDL3/SDL_dialog.h +++ b/include/SDL3/SDL_dialog.h @@ -37,7 +37,9 @@ extern "C" { * `name` is a user-readable label for the filter (for example, "Office document"). * * `pattern` is a semicolon-separated list of file extensions (for example, - * "doc;docx"). + * "doc;docx"). File extensions may only contain alphanumeric characters, + * hyphens, underscores and periods. Alternatively, the whole string can be a + * single asterisk ("*"), which serves as an "All files" filter. * * \sa SDL_DialogFileCallback * \sa SDL_ShowOpenFileDialog diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 5c04617ad..5b34233d6 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -414,6 +414,26 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_DIRECTINPUT "SDL_JOYSTICK_DIRECTINPUT" +/** + * A variable that specifies a dialog backend to use. + * + * By default, SDL will try all available dialog backends in a reasonable order until it finds one that can work, but this hint allows the app or user to force a specific target. + * + * If the specified target does not exist or is not available, the dialog-related function calls will fail. + * + * This hint currently only applies to platforms using the generic "Unix" dialog implementation, but may be extended to more platforms in the future. Note that some Unix and Unix-like platforms have their own implementation, such as macOS and Haiku. + * + * The variable can be set to the following values: + * NULL - Select automatically (default, all platforms) + * "portal" - Use XDG Portals through DBus (Unix only) + * "zenity" - Use the Zenity program (Unix only) + * + * More options may be added in the future. + * + * This hint can be set anytime. + */ +#define SDL_HINT_FILE_DIALOG_DRIVER "SDL_FILE_DIALOG_DRIVER" + /** * Override for SDL_GetDisplayUsableBounds() * diff --git a/src/dialog/SDL_dialog_utils.c b/src/dialog/SDL_dialog_utils.c new file mode 100644 index 000000000..5d29a23fe --- /dev/null +++ b/src/dialog/SDL_dialog_utils.c @@ -0,0 +1,237 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include "SDL_dialog_utils.h" + +char *convert_filters(const SDL_DialogFileFilter *filters, NameTransform ntf, + const char *prefix, const char *separator, + const char *suffix, const char *filt_prefix, + const char *filt_separator, const char *filt_suffix, + const char *ext_prefix, const char *ext_separator, + const char *ext_suffix) +{ + char *combined; + char *new_combined; + char *converted; + const char *terminator; + int new_length; + + combined = SDL_strdup(prefix); + + if (!combined) { + SDL_OutOfMemory(); + return NULL; + } + + for (const SDL_DialogFileFilter *f = filters; f->name; f++) { + converted = convert_filter(*f, ntf, filt_prefix, filt_separator, + filt_suffix, ext_prefix, ext_separator, + ext_suffix); + + if (!converted) { + return NULL; + } + + terminator = f[1].name ? separator : suffix; + new_length = SDL_strlen(combined) + SDL_strlen(converted) + + SDL_strlen(terminator); + + new_combined = SDL_realloc(combined, new_length); + + if (!new_combined) { + SDL_free(converted); + SDL_free(combined); + SDL_OutOfMemory(); + return NULL; + } + + combined = new_combined; + + SDL_strlcat(combined, converted, new_length); + SDL_strlcat(combined, terminator, new_length); + } + + return combined; +} + +char *convert_filter(const SDL_DialogFileFilter filter, NameTransform ntf, + const char *prefix, const char *separator, + const char *suffix, const char *ext_prefix, + const char *ext_separator, const char *ext_suffix) +{ + char *converted; + char *name_filtered; + int total_length; + char *list; + + list = convert_ext_list(filter.pattern, ext_prefix, ext_separator, + ext_suffix); + + if (!list) { + return NULL; + } + + if (ntf) { + name_filtered = ntf(filter.name); + } else { + /* Useless strdup, but easier to read and maintain code this way */ + name_filtered = SDL_strdup(filter.name); + } + + if (!name_filtered) { + SDL_free(list); + return NULL; + } + + total_length = SDL_strlen(prefix) + SDL_strlen(name_filtered) + + SDL_strlen(separator) + SDL_strlen(list) + + SDL_strlen(suffix) + 1; + + converted = (char *) SDL_malloc(total_length); + + if (!converted) { + SDL_free(list); + SDL_free(name_filtered); + SDL_OutOfMemory(); + return NULL; + } + + SDL_snprintf(converted, total_length, "%s%s%s%s%s", prefix, name_filtered, + separator, list, suffix); + + SDL_free(list); + SDL_free(name_filtered); + + return converted; +} + +char *convert_ext_list(const char *list, const char *prefix, + const char *separator, const char *suffix) +{ + char *converted; + int semicolons; + int total_length; + + semicolons = 0; + + for (const char *c = list; *c; c++) { + semicolons += (*c == ';'); + } + + total_length = + SDL_strlen(list) - semicolons /* length of list contents */ + + semicolons * SDL_strlen(separator) /* length of separators */ + + SDL_strlen(prefix) + SDL_strlen(suffix) /* length of prefix/suffix */ + + 1; /* terminating null byte */ + + converted = (char *) SDL_malloc(total_length); + + if (!converted) { + SDL_OutOfMemory(); + return NULL; + } + + *converted = '\0'; + + SDL_strlcat(converted, prefix, total_length); + + /* Some platforms may prefer to handle the asterisk manually, but this + function offers to handle it for ease of use. */ + if (SDL_strcmp(list, "*") == 0) { + SDL_strlcat(converted, "*", total_length); + } else { + for (const char *c = list; *c; c++) { + if ((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') + || (*c >= '0' && *c <= '9') || *c == '-' || *c == '_' + || *c == '.') { + char str[2]; + str[0] = *c; + str[1] = '\0'; + SDL_strlcat(converted, str, total_length); + } else if (*c == ';') { + if (c == list || c[-1] == ';') { + SDL_SetError("Empty pattern not allowed"); + SDL_free(converted); + return NULL; + } + + SDL_strlcat(converted, separator, total_length); + } else { + SDL_SetError("Invalid character '%c' in pattern (Only [a-zA-Z0-9_.-] allowed, or a single *)", *c); + SDL_free(converted); + return NULL; + } + } + } + + if (list[SDL_strlen(list) - 1] == ';') { + SDL_SetError("Empty pattern not allowed"); + SDL_free(converted); + return NULL; + } + + SDL_strlcat(converted, suffix, total_length); + + return converted; +} + +const char *validate_filters(const SDL_DialogFileFilter *filters) +{ + if (filters) { + for (const SDL_DialogFileFilter *f = filters; f->name; f++) { + const char *msg = validate_list(f->pattern); + + if (msg) { + return msg; + } + } + } + + return NULL; +} + +const char *validate_list(const char *list) +{ + if (SDL_strcmp(list, "*") == 0) { + return NULL; + } else { + for (const char *c = list; *c; c++) { + if ((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') + || (*c >= '0' && *c <= '9') || *c == '-' || *c == '_' + || *c == '.') { + continue; + } else if (*c == ';') { + if (c == list || c[-1] == ';') { + return "Empty pattern not allowed"; + } + } else { + return "Invalid character in pattern (Only [a-zA-Z0-9_.-] allowed, or a single *)"; + } + } + } + + if (list[SDL_strlen(list) - 1] == ';') { + return "Empty pattern not allowed"; + } + + return NULL; +} diff --git a/src/dialog/SDL_dialog_utils.h b/src/dialog/SDL_dialog_utils.h new file mode 100644 index 000000000..ed9fd3111 --- /dev/null +++ b/src/dialog/SDL_dialog_utils.h @@ -0,0 +1,57 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +/* The following are utility functions to help implementations. + They are ordered by scope largeness, decreasing. All implementations + should use them, as they check for invalid filters. Where they are unused, + the validate_* function further down below should be used. */ + +/* Transform the name given in argument into something viable for the engine. + Useful if there are special characters to avoid on certain platforms (such + as "|" with Zenity). */ +typedef char *(NameTransform)(const char * name); + +/* Converts all the filters into a single string. */ +/* [filter]{[filter]...} */ +char *convert_filters(const SDL_DialogFileFilter *filters, NameTransform ntf, + const char *prefix, const char *separator, + const char *suffix, const char *filt_prefix, + const char *filt_separator, const char *filt_suffix, + const char *ext_prefix, const char *ext_separator, + const char *ext_suffix); + +/* Converts one filter into a single string. */ +/* [filter name][filter extension list] */ +char *convert_filter(const SDL_DialogFileFilter filter, NameTransform ntf, + const char *prefix, const char *separator, + const char *suffix, const char *ext_prefix, + const char *ext_separator, const char *ext_suffix); + +/* Converts the extenstion list of a filter into a single string. */ +/* [extension]{[extension]...} */ +char *convert_ext_list(const char *list, const char *prefix, + const char *suffix, const char *separator); + +/* Must be used if convert_* functions aren't used */ +/* Returns an error message if there's a problem, NULL otherwise */ +const char *validate_filters(const SDL_DialogFileFilter *filters); +const char *validate_list(const char *list); diff --git a/src/dialog/cocoa/SDL_cocoadialog.m b/src/dialog/cocoa/SDL_cocoadialog.m index 970b1884f..be596cecc 100644 --- a/src/dialog/cocoa/SDL_cocoadialog.m +++ b/src/dialog/cocoa/SDL_cocoadialog.m @@ -19,6 +19,7 @@ 3. This notice may not be removed or altered from any source distribution. */ #include "SDL_internal.h" +#include "../SDL_dialog_utils.h" #import #import @@ -36,6 +37,20 @@ void show_file_dialog(cocoa_FileDialogType type, SDL_DialogFileCallback callback SDL_SetError("tvOS and iOS don't support path-based file dialogs"); callback(userdata, NULL, -1); #else + const char *msg = validate_filters(filters); + + if (msg) { + SDL_SetError("%s", msg); + callback(userdata, NULL, -1); + return; + } + + if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) { + SDL_SetError("File dialog driver unsupported"); + callback(userdata, NULL, -1); + return; + } + /* NSOpenPanel inherits from NSSavePanel */ NSSavePanel *dialog; NSOpenPanel *dialog_as_open; @@ -83,10 +98,6 @@ void show_file_dialog(cocoa_FileDialogType type, SDL_DialogFileCallback callback [types addObject: [NSString stringWithFormat: @"%s", pattern_ptr]]; } pattern_ptr = c + 1; - } else if (!((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') || (*c >= '0' && *c <= '9') || *c == '.' || *c == '_' || *c == '-' || (*c == '*' && (c[1] == '\0' || c[1] == ';')))) { - SDL_SetError("Illegal character in pattern name: %c (Only alphanumeric characters, periods, underscores and hyphens allowed)", *c); - callback(userdata, NULL, -1); - SDL_free(pattern); } else if (*c == '*') { has_all_files = 1; } diff --git a/src/dialog/haiku/SDL_haikudialog.cc b/src/dialog/haiku/SDL_haikudialog.cc index 3e63fc807..ac28415c6 100644 --- a/src/dialog/haiku/SDL_haikudialog.cc +++ b/src/dialog/haiku/SDL_haikudialog.cc @@ -19,6 +19,9 @@ 3. This notice may not be removed or altered from any source distribution. */ #include "SDL_internal.h" +extern "C" { +#include "../SDL_dialog_utils.h" +} #include "../../core/haiku/SDL_BeApp.h" #include @@ -197,10 +200,33 @@ void ShowDialog(bool save, SDL_DialogFileCallback callback, void *userdata, bool return; } + const char *msg = validate_filters(filters); + + if (msg) { + SDL_SetError("%s", msg); + callback(userdata, NULL, -1); + return; + } + + if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) { + SDL_SetError("File dialog driver unsupported"); + callback(userdata, NULL, -1); + return; + } + // No unique_ptr's because they need to survive the end of the function - CallbackLooper *looper = new CallbackLooper(callback, userdata); - BMessenger *messenger = new BMessenger(NULL, looper); - SDLBRefFilter *filter = new SDLBRefFilter(filters); + CallbackLooper *looper = new(std::nothrow) CallbackLooper(callback, userdata); + BMessenger *messenger = new(std::nothrow) BMessenger(NULL, looper); + SDLBRefFilter *filter = new(std::nothrow) SDLBRefFilter(filters); + + if (looper == NULL || messenger == NULL || filter == NULL) { + SDL_free(looper); + SDL_free(messenger); + SDL_free(filter); + SDL_OutOfMemory(); + callback(userdata, NULL, -1); + return; + } BEntry entry; entry_ref entryref; diff --git a/src/dialog/unix/SDL_portaldialog.c b/src/dialog/unix/SDL_portaldialog.c index e56478bb0..7f28cb6d2 100644 --- a/src/dialog/unix/SDL_portaldialog.c +++ b/src/dialog/unix/SDL_portaldialog.c @@ -19,7 +19,7 @@ 3. This notice may not be removed or altered from any source distribution. */ #include "SDL_internal.h" -#include "./SDL_dialog.h" +#include "../SDL_dialog_utils.h" #include "../../core/linux/SDL_dbus.h" @@ -270,6 +270,14 @@ static void DBus_OpenDialog(const char *method, const char *method_title, SDL_Di static char *default_parent_window = ""; SDL_PropertiesID props = SDL_GetWindowProperties(window); + const char *err_msg = validate_filters(filters); + + if (err_msg) { + SDL_SetError("%s", err_msg); + callback(userdata, NULL, -1); + return; + } + if (dbus == NULL) { SDL_SetError("Failed to connect to DBus"); return; diff --git a/src/dialog/unix/SDL_unixdialog.c b/src/dialog/unix/SDL_unixdialog.c index 29ce2eef0..ae57833fe 100644 --- a/src/dialog/unix/SDL_unixdialog.c +++ b/src/dialog/unix/SDL_unixdialog.c @@ -27,31 +27,56 @@ static void (*detected_open)(SDL_DialogFileCallback callback, void* userdata, SD static void (*detected_save)(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location) = NULL; static void (*detected_folder)(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, SDL_bool allow_many) = NULL; -/* Returns non-zero on success, 0 on failure */ -static int detect_available_methods(void) +static int detect_available_methods(const char *value); + +void SDLCALL hint_callback(void *userdata, const char *name, const char *oldValue, const char *newValue) { - if (SDL_Portal_detect()) { - detected_open = SDL_Portal_ShowOpenFileDialog; - detected_save = SDL_Portal_ShowSaveFileDialog; - detected_folder = SDL_Portal_ShowOpenFolderDialog; - return 1; + detect_available_methods(newValue); +} + +static void set_callback(void) +{ + static SDL_bool is_set = SDL_FALSE; + + if (is_set == SDL_FALSE) { + is_set = SDL_TRUE; + SDL_AddHintCallback(SDL_HINT_FILE_DIALOG_DRIVER, hint_callback, NULL); + } +} + +/* Returns non-zero on success, 0 on failure */ +static int detect_available_methods(const char *value) +{ + const char *driver = value ? value : SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER); + + set_callback(); + + if (driver == NULL || SDL_strcmp(driver, "portal") == 0) { + if (SDL_Portal_detect()) { + detected_open = SDL_Portal_ShowOpenFileDialog; + detected_save = SDL_Portal_ShowSaveFileDialog; + detected_folder = SDL_Portal_ShowOpenFolderDialog; + return 1; + } } - if (SDL_Zenity_detect()) { - detected_open = SDL_Zenity_ShowOpenFileDialog; - detected_save = SDL_Zenity_ShowSaveFileDialog; - detected_folder = SDL_Zenity_ShowOpenFolderDialog; - return 2; + if (driver == NULL || SDL_strcmp(driver, "zenity") == 0) { + if (SDL_Zenity_detect()) { + detected_open = SDL_Zenity_ShowOpenFileDialog; + detected_save = SDL_Zenity_ShowSaveFileDialog; + detected_folder = SDL_Zenity_ShowOpenFolderDialog; + return 2; + } } - SDL_SetError("No supported method for file dialogs"); + SDL_SetError("File dialog driver unsupported"); return 0; } void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location, SDL_bool allow_many) { /* Call detect_available_methods() again each time in case the situation changed */ - if (!detected_open && !detect_available_methods()) { + if (!detected_open && !detect_available_methods(NULL)) { /* SetError() done by detect_available_methods() */ callback(userdata, NULL, -1); return; @@ -63,7 +88,7 @@ void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location) { /* Call detect_available_methods() again each time in case the situation changed */ - if (!detected_save && !detect_available_methods()) { + if (!detected_save && !detect_available_methods(NULL)) { /* SetError() done by detect_available_methods() */ callback(userdata, NULL, -1); return; @@ -75,7 +100,7 @@ void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, SDL_bool allow_many) { /* Call detect_available_methods() again each time in case the situation changed */ - if (!detected_folder && !detect_available_methods()) { + if (!detected_folder && !detect_available_methods(NULL)) { /* SetError() done by detect_available_methods() */ callback(userdata, NULL, -1); return; diff --git a/src/dialog/unix/SDL_zenitydialog.c b/src/dialog/unix/SDL_zenitydialog.c index 5ba8e8dc7..cef8826c5 100644 --- a/src/dialog/unix/SDL_zenitydialog.c +++ b/src/dialog/unix/SDL_zenitydialog.c @@ -19,7 +19,7 @@ 3. This notice may not be removed or altered from any source distribution. */ #include "SDL_internal.h" -#include "./SDL_dialog.h" +#include "../SDL_dialog_utils.h" #include #include @@ -65,6 +65,22 @@ typedef struct } \ } +char *zenity_clean_name(const char *name) +{ + char *newname = SDL_strdup(name); + + /* Filter out "|", which Zenity considers a special character. Let's hope + there aren't others. TODO: find something better. */ + for (char *c = newname; *c; c++) { + if (*c == '|') { + /* Zenity doesn't support escaping with \ */ + *c = '/'; + } + } + + return newname; +} + /* Exec call format: * * /usr/bin/env zenity --file-selection --separator=\n [--multiple] @@ -147,68 +163,15 @@ static char** generate_args(const zenityArgs* info) const SDL_DialogFileFilter *filter_ptr = info->filters; while (filter_ptr->name && filter_ptr->pattern) { - /* *Normally*, no filter arg should exceed 4096 bytes. */ - char buffer[4096]; + char *filter_str = convert_filter(*filter_ptr, zenity_clean_name, + "--file-filter=", " | ", "", + "*.", " *.", ""); - SDL_snprintf(buffer, 4096, "--file-filter=%s | *.", filter_ptr->name); - size_t i_buf = SDL_strlen(buffer); - - /* "|" is a special character for Zenity */ - for (char *c = buffer; *c; c++) { - if (*c == '|') { - *c = ' '; - } - } - - for (size_t i_pat = 0; i_buf < 4095 && filter_ptr->pattern[i_pat]; i_pat++) { - const char *c = filter_ptr->pattern + i_pat; - - if (*c == ';') { - /* Disallow empty patterns (might bug Zenity) */ - int at_end = (c[1] == '\0'); - int at_mid = (c[1] == ';'); - int at_beg = (i_pat == 0); - if (at_end || at_mid || at_beg) { - const char *pos_str = ""; - - if (at_end) { - pos_str = "end"; - } else if (at_mid) { - pos_str = "middle"; - } else if (at_beg) { - pos_str = "beginning"; - } - - SDL_SetError("Empty pattern file extension (at %s of list)", pos_str); - CLEAR_AND_RETURN() - } - - if (i_buf + 3 >= 4095) { - i_buf += 3; - break; - } - - buffer[i_buf++] = ' '; - buffer[i_buf++] = '*'; - buffer[i_buf++] = '.'; - } else if (*c == '*' && (c[1] == '\0' || c[1] == ';') && (i_pat == 0 || *(c - 1) == ';')) { - buffer[i_buf++] = '*'; - } else if (!((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') || (*c >= '0' && *c <= '9') || *c == '.' || *c == '_' || *c == '-')) { - SDL_SetError("Illegal character in pattern name: %c (Only alphanumeric characters, periods, underscores and hyphens allowed)", *c); - CLEAR_AND_RETURN() - } else { - buffer[i_buf++] = *c; - } - } - - if (i_buf >= 4095) { - SDL_SetError("Filter '%s' wouldn't fit in a 4096 byte buffer; please report your use case if you need filters that long", filter_ptr->name); + if (!filter_str) { CLEAR_AND_RETURN() } - buffer[i_buf] = '\0'; - - argv[nextarg++] = SDL_strdup(buffer); + argv[nextarg++] = filter_str; CHECK_OOM() filter_ptr++; diff --git a/src/dialog/windows/SDL_windowsdialog.c b/src/dialog/windows/SDL_windowsdialog.c index 61fde649b..3c766993b 100644 --- a/src/dialog/windows/SDL_windowsdialog.c +++ b/src/dialog/windows/SDL_windowsdialog.c @@ -19,6 +19,7 @@ 3. This notice may not be removed or altered from any source distribution. */ #include "SDL_internal.h" +#include "../SDL_dialog_utils.h" #include #include @@ -122,61 +123,42 @@ void windows_ShowFileDialog(void *ptr) MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, default_folder, -1, filebuffer, MAX_PATH); } - size_t len = 0; - for (const SDL_DialogFileFilter *filter = filters; filter && filter->name && filter->pattern; filter++) { - const char *pattern_ptr = filter->pattern; - len += SDL_strlen(filter->name) + SDL_strlen(filter->pattern) + 4; - while (*pattern_ptr) { - if (*pattern_ptr == ';') { - len += 2; - } - pattern_ptr++; - } - } - wchar_t *filterlist = SDL_malloc((len + 1) * sizeof(wchar_t)); + /* '\x01' is used in place of a null byte */ + char *filterlist = convert_filters(filters, NULL, "", "", "\x01", "", + "\x01", "\x01", "*.", ";*.", ""); if (!filterlist) { - SDL_OutOfMemory(); callback(userdata, NULL, -1); return; } - wchar_t *filter_ptr = filterlist; - for (const SDL_DialogFileFilter *filter = filters; filter && filter->name && filter->pattern; filter++) { - size_t l = SDL_strlen(filter->name); - const char *pattern_ptr = filter->pattern; + int filter_len = SDL_strlen(filterlist); - MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, filter->name, -1, filter_ptr, MAX_PATH); - filter_ptr += l + 1; - - *filter_ptr++ = L'*'; - *filter_ptr++ = L'.'; - while (*pattern_ptr) { - if (*pattern_ptr == ';') { - *filter_ptr++ = L';'; - *filter_ptr++ = L'*'; - *filter_ptr++ = L'.'; - } else if (*pattern_ptr == '*' && (pattern_ptr[1] == '\0' || pattern_ptr[1] == ';')) { - *filter_ptr++ = L'*'; - } else if (!((*pattern_ptr >= 'a' && *pattern_ptr <= 'z') || (*pattern_ptr >= 'A' && *pattern_ptr <= 'Z') || (*pattern_ptr >= '0' && *pattern_ptr <= '9') || *pattern_ptr == '.' || *pattern_ptr == '_' || *pattern_ptr == '-')) { - SDL_SetError("Illegal character in pattern name: %c (Only alphanumeric characters, periods, underscores and hyphens allowed)", *pattern_ptr); - callback(userdata, NULL, -1); - } else { - MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, pattern_ptr, 1, filter_ptr, 1); - filter_ptr++; - } - pattern_ptr++; + for (char *c = filterlist; *c; c++) { + if (*c == '\x01') { + *c = '\0'; } - *filter_ptr++ = '\0'; } - *filter_ptr = '\0'; + int filter_wlen = MultiByteToWideChar(CP_UTF8, 0, filterlist, filter_len, NULL, 0); + wchar_t *filter_wchar = SDL_malloc(filter_wlen * sizeof(wchar_t)); + + if (!filter_wchar) { + SDL_OutOfMemory(); + SDL_free(filterlist); + callback(userdata, NULL, -1); + return; + } + + MultiByteToWideChar(CP_UTF8, 0, filterlist, filter_len, filter_wchar, filter_wlen); + + SDL_free(filterlist); OPENFILENAMEW dialog; dialog.lStructSize = sizeof(OPENFILENAME); dialog.hwndOwner = window; dialog.hInstance = 0; - dialog.lpstrFilter = filterlist; + dialog.lpstrFilter = filter_wchar; dialog.lpstrCustomFilter = NULL; dialog.nMaxCustFilter = 0; dialog.nFilterIndex = 0; @@ -198,7 +180,7 @@ void windows_ShowFileDialog(void *ptr) BOOL result = pGetAnyFileName(&dialog); - SDL_free(filterlist); + SDL_free(filter_wchar); if (result) { if (!(flags & OFN_ALLOWMULTISELECT)) { @@ -401,6 +383,13 @@ void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL winArgs *args; SDL_Thread *thread; + if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) { + SDL_Log("%s", SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER)); + SDL_SetError("File dialog driver unsupported"); + callback(userdata, NULL, -1); + return; + } + args = SDL_malloc(sizeof(winArgs)); if (args == NULL) { SDL_OutOfMemory(); @@ -421,6 +410,7 @@ void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL if (thread == NULL) { callback(userdata, NULL, -1); + SDL_free(args); return; } @@ -432,6 +422,12 @@ void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL winArgs *args; SDL_Thread *thread; + if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) { + SDL_SetError("File dialog driver unsupported"); + callback(userdata, NULL, -1); + return; + } + args = SDL_malloc(sizeof(winArgs)); if (args == NULL) { SDL_OutOfMemory(); @@ -452,6 +448,7 @@ void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL if (thread == NULL) { callback(userdata, NULL, -1); + SDL_free(args); return; } @@ -463,6 +460,12 @@ void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, S winFArgs *args; SDL_Thread *thread; + if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) { + SDL_SetError("File dialog driver unsupported"); + callback(userdata, NULL, -1); + return; + } + args = SDL_malloc(sizeof(winFArgs)); if (args == NULL) { SDL_OutOfMemory(); @@ -479,6 +482,7 @@ void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, S if (thread == NULL) { callback(userdata, NULL, -1); + SDL_free(args); return; }