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;
}