浏览代码

SDL_ShowFileDialogWithProperties with more options

Semphris 6 月之前
父节点
当前提交
a4852f3a10

+ 1 - 0
Android.mk

@@ -30,6 +30,7 @@ LOCAL_SRC_FILES := \
 	$(wildcard $(LOCAL_PATH)/src/core/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/core/android/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/cpuinfo/*.c) \
+	$(LOCAL_PATH)/src/dialog/SDL_dialog.c \
 	$(LOCAL_PATH)/src/dialog/SDL_dialog_utils.c \
 	$(LOCAL_PATH)/src/dialog/android/SDL_androiddialog.c \
 	$(wildcard $(LOCAL_PATH)/src/dynapi/*.c) \

+ 1 - 0
CMakeLists.txt

@@ -2874,6 +2874,7 @@ endif()
 
 if (SDL_DIALOG)
   sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/SDL_dialog_utils.c)
+  sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/SDL_dialog.c)
   if(ANDROID)
     sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/android/SDL_androiddialog.c)
     set(HAVE_SDL_DIALOG TRUE)

+ 1 - 0
VisualC-GDK/SDL/SDL.vcxproj

@@ -517,6 +517,7 @@
     </ClCompile>
     <ClCompile Include="..\..\src\camera\dummy\SDL_camera_dummy.c" />
     <ClCompile Include="..\..\src\camera\SDL_camera.c" />
+    <ClCompile Include="..\..\src\dialog\SDL_dialog.c" />
     <ClCompile Include="..\..\src\dialog\SDL_dialog_utils.c" />
     <ClCompile Include="..\..\src\filesystem\SDL_filesystem.c" />
     <ClCompile Include="..\..\src\filesystem\windows\SDL_sysfsops.c" />

+ 3 - 0
VisualC-GDK/SDL/SDL.vcxproj.filters

@@ -7,6 +7,9 @@
     <ClCompile Include="..\..\src\dialog\SDL_dialog_utils.c">
       <Filter>dialog</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\dialog\SDL_dialog.c">
+      <Filter>dialog</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\filesystem\SDL_filesystem.c">
       <Filter>filesystem</Filter>
     </ClCompile>

+ 1 - 0
VisualC/SDL/SDL.vcxproj

@@ -412,6 +412,7 @@
     <ClCompile Include="..\..\src\camera\dummy\SDL_camera_dummy.c" />
     <ClCompile Include="..\..\src\camera\mediafoundation\SDL_camera_mediafoundation.c" />
     <ClCompile Include="..\..\src\camera\SDL_camera.c" />
+    <ClCompile Include="..\..\src\dialog\SDL_dialog.c" />
     <ClCompile Include="..\..\src\dialog\SDL_dialog_utils.c" />
     <ClCompile Include="..\..\src\filesystem\SDL_filesystem.c" />
     <ClCompile Include="..\..\src\filesystem\windows\SDL_sysfsops.c" />

+ 3 - 0
VisualC/SDL/SDL.vcxproj.filters

@@ -950,6 +950,9 @@
     <ClCompile Include="..\..\src\camera\SDL_camera.c">
       <Filter>camera</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\dialog\SDL_dialog.c">
+      <Filter>dialog</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\dialog\SDL_dialog_utils.c">
       <Filter>dialog</Filter>
     </ClCompile>

+ 86 - 8
include/SDL3/SDL_dialog.h

@@ -30,6 +30,7 @@
 
 #include <SDL3/SDL_stdinc.h>
 #include <SDL3/SDL_error.h>
+#include <SDL3/SDL_properties.h>
 #include <SDL3/SDL_video.h>
 
 #include <SDL3/SDL_begin_code.h>
@@ -55,6 +56,7 @@ extern "C" {
  * \sa SDL_ShowOpenFileDialog
  * \sa SDL_ShowSaveFileDialog
  * \sa SDL_ShowOpenFolderDialog
+ * \sa SDL_ShowFileDialogWithProperties
  */
 typedef struct SDL_DialogFileFilter
 {
@@ -93,14 +95,13 @@ typedef struct SDL_DialogFileFilter
  * \sa SDL_ShowOpenFileDialog
  * \sa SDL_ShowSaveFileDialog
  * \sa SDL_ShowOpenFolderDialog
+ * \sa SDL_ShowFileDialogWithProperties
  */
 typedef void (SDLCALL *SDL_DialogFileCallback)(void *userdata, const char * const *filelist, int filter);
 
 /**
  * Displays a dialog that lets the user select a file on their filesystem.
  *
- * This function should only be invoked from the main thread.
- *
  * This is an asynchronous function; it will return immediately, and the
  * result will be passed to the callback.
  *
@@ -127,19 +128,25 @@ typedef void (SDLCALL *SDL_DialogFileCallback)(void *userdata, const char * cons
  *               Not all platforms support this option.
  * \param filters a list of filters, may be NULL. Not all platforms support
  *                this option, and platforms that do support it may allow the
- *                user to ignore the filters.
+ *                user to ignore the filters. If non-NULL, it must remain valid
+ *                at least until the callback is invoked.
  * \param nfilters the number of filters. Ignored if filters is NULL.
  * \param default_location the default folder or file to start the dialog at,
  *                         may be NULL. Not all platforms support this option.
  * \param allow_many if non-zero, the user will be allowed to select multiple
  *                   entries. Not all platforms support this option.
  *
+ * \threadsafety This function should be called only from the main thread. The
+ *               callback may be invoked from the same thread or from a
+ *               different one, depending on the OS's constraints.
+ *
  * \since This function is available since SDL 3.1.3.
  *
  * \sa SDL_DialogFileCallback
  * \sa SDL_DialogFileFilter
  * \sa SDL_ShowSaveFileDialog
  * \sa SDL_ShowOpenFolderDialog
+ * \sa SDL_ShowFileDialogWithProperties
  */
 extern SDL_DECLSPEC void SDLCALL SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location, bool allow_many);
 
@@ -147,8 +154,6 @@ extern SDL_DECLSPEC void SDLCALL SDL_ShowOpenFileDialog(SDL_DialogFileCallback c
  * Displays a dialog that lets the user choose a new or existing file on their
  * filesystem.
  *
- * This function should only be invoked from the main thread.
- *
  * This is an asynchronous function; it will return immediately, and the
  * result will be passed to the callback.
  *
@@ -174,25 +179,29 @@ extern SDL_DECLSPEC void SDLCALL SDL_ShowOpenFileDialog(SDL_DialogFileCallback c
  *               Not all platforms support this option.
  * \param filters a list of filters, may be NULL. Not all platforms support
  *                this option, and platforms that do support it may allow the
- *                user to ignore the filters.
+ *                user to ignore the filters. If non-NULL, it must remain valid
+ *                at least until the callback is invoked.
  * \param nfilters the number of filters. Ignored if filters is NULL.
  * \param default_location the default folder or file to start the dialog at,
  *                         may be NULL. Not all platforms support this option.
  *
+ * \threadsafety This function should be called only from the main thread. The
+ *               callback may be invoked from the same thread or from a
+ *               different one, depending on the OS's constraints.
+ *
  * \since This function is available since SDL 3.1.3.
  *
  * \sa SDL_DialogFileCallback
  * \sa SDL_DialogFileFilter
  * \sa SDL_ShowOpenFileDialog
  * \sa SDL_ShowOpenFolderDialog
+ * \sa SDL_ShowFileDialogWithProperties
  */
 extern SDL_DECLSPEC void SDLCALL SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location);
 
 /**
  * Displays a dialog that lets the user select a folder on their filesystem.
  *
- * This function should only be invoked from the main thread.
- *
  * This is an asynchronous function; it will return immediately, and the
  * result will be passed to the callback.
  *
@@ -222,14 +231,83 @@ extern SDL_DECLSPEC void SDLCALL SDL_ShowSaveFileDialog(SDL_DialogFileCallback c
  * \param allow_many if non-zero, the user will be allowed to select multiple
  *                   entries. Not all platforms support this option.
  *
+ * \threadsafety This function should be called only from the main thread. The
+ *               callback may be invoked from the same thread or from a
+ *               different one, depending on the OS's constraints.
+ *
  * \since This function is available since SDL 3.1.3.
  *
  * \sa SDL_DialogFileCallback
  * \sa SDL_ShowOpenFileDialog
  * \sa SDL_ShowSaveFileDialog
+ * \sa SDL_ShowFileDialogWithProperties
  */
 extern SDL_DECLSPEC void SDLCALL SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const char *default_location, bool allow_many);
 
+typedef enum SDL_FileDialogType {
+  SDL_FILEDIALOG_OPENFILE,
+  SDL_FILEDIALOG_SAVEFILE,
+  SDL_FILEDIALOG_OPENFOLDER
+} SDL_FileDialogType;
+
+#define SDL_PROP_FILE_DIALOG_FILTERS_POINTER     "SDL.filedialog.filters"
+#define SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER     "SDL.filedialog.nfilters"
+#define SDL_PROP_FILE_DIALOG_WINDOW_POINTER      "SDL.filedialog.window"
+#define SDL_PROP_FILE_DIALOG_LOCATION_STRING     "SDL.filedialog.location"
+#define SDL_PROP_FILE_DIALOG_MANY_BOOLEAN        "SDL.filedialog.many"
+#define SDL_PROP_FILE_DIALOG_TITLE_STRING        "SDL.filedialog.title"
+#define SDL_PROP_FILE_DIALOG_ACCEPT_STRING       "SDL.filedialog.accept"
+#define SDL_PROP_FILE_DIALOG_CANCEL_STRING       "SDL.filedialog.cancel"
+
+/**
+ * Create and launch a file dialog with the specified properties.
+ *
+ * These are the supported properties:
+ *
+ * - `SDL_PROP_FILE_DIALOG_FILTERS_POINTER`: a pointer to a list of
+ *   SDL_DialogFileFilter's, which will be used as filters for file-based
+ *   selections. Ignored if the dialog is an "Open Folder" dialog. If non-NULL,
+ *   the array of filters must remain valid at least until the callback is
+ *   invoked.
+ * - `SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER`: the number of filters in the array
+ *   of filters, if it exists.
+ * - `SDL_PROP_FILE_DIALOG_WINDOW_POINTER`: the window that the dialog should
+ *   be modal for.
+ * - `SDL_PROP_FILE_DIALOG_LOCATION_STRING`: the default folder or file to
+ *   start the dialog at.
+ * - `SDL_PROP_FILE_DIALOG_MANY_BOOLEAN`: true to allow the user to select more
+ *   than one entry.
+ * - `SDL_PROP_FILE_DIALOG_TITLE_STRING`: the title for the dialog.
+ * - `SDL_PROP_FILE_DIALOG_ACCEPT_STRING`: the label that the accept button
+ *   should have.
+ * - `SDL_PROP_FILE_DIALOG_CANCEL_STRING`: the label that the cancel button
+ *   should have.
+ *
+ * Note that each platform may or may not support any of the properties.
+ *
+ * \param type the type of file dialog.
+ * \param callback a function pointer to be invoked when the user selects a
+ *                 file and accepts, or cancels the dialog, or an error
+ *                 occurs.
+ * \param userdata an optional pointer to pass extra data to the callback when
+ *                 it will be invoked.
+ * \param props the properties to use.
+ *
+ * \threadsafety This function should be called only from the main thread. The
+ *               callback may be invoked from the same thread or from a
+ *               different one, depending on the OS's constraints.
+ *
+ * \since This function is available since SDL 3.2.0.
+ *
+ * \sa SDL_FileDialogType
+ * \sa SDL_DialogFileCallback
+ * \sa SDL_DialogFileFilter
+ * \sa SDL_ShowOpenFileDialog
+ * \sa SDL_ShowSaveFileDialog
+ * \sa SDL_ShowOpenFolderDialog
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props);
+
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 }

+ 103 - 0
src/dialog/SDL_dialog.c

@@ -0,0 +1,103 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+  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.h"
+#include "SDL_dialog_utils.h"
+
+void SDL_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
+{
+    if (!callback) {
+        return;
+    }
+
+    SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
+    int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, -1);
+
+    if (filters && nfilters == -1) {
+        SDL_SetError("Set filter pointers, but didn't set number of filters (SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER)");
+        callback(userdata, NULL, -1);
+        return;
+    }
+
+    const char *msg = validate_filters(filters, nfilters);
+
+    if (msg) {
+        SDL_SetError("Invalid dialog file filters: %s", msg);
+        callback(userdata, NULL, -1);
+        return;
+    }
+
+    switch (type) {
+    case SDL_FILEDIALOG_OPENFILE:
+    case SDL_FILEDIALOG_SAVEFILE:
+    case SDL_FILEDIALOG_OPENFOLDER:
+        SDL_SYS_ShowFileDialogWithProperties(type, callback, userdata, props);
+        break;
+
+    default:
+        SDL_SetError("Unsupported file dialog type: %d", (int) type);
+        callback(userdata, NULL, -1);
+        break;
+    };
+}
+
+void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location, bool allow_many)
+{
+    SDL_PropertiesID props = SDL_CreateProperties();
+
+    SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, (void *) filters);
+    SDL_SetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, nfilters);
+    SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, window);
+    SDL_SetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, default_location);
+    SDL_SetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, allow_many);
+
+    SDL_ShowFileDialogWithProperties(SDL_FILEDIALOG_OPENFILE, callback, userdata, props);
+
+    SDL_DestroyProperties(props);
+}
+
+void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location)
+{
+    SDL_PropertiesID props = SDL_CreateProperties();
+
+    SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, (void *) filters);
+    SDL_SetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, nfilters);
+    SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, window);
+    SDL_SetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, default_location);
+
+    SDL_ShowFileDialogWithProperties(SDL_FILEDIALOG_SAVEFILE, callback, userdata, props);
+
+    SDL_DestroyProperties(props);
+}
+
+void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const char *default_location, bool allow_many)
+{
+    SDL_PropertiesID props = SDL_CreateProperties();
+
+    SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, window);
+    SDL_SetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, default_location);
+    SDL_SetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, allow_many);
+
+    SDL_ShowFileDialogWithProperties(SDL_FILEDIALOG_OPENFOLDER, callback, userdata, props);
+
+    SDL_DestroyProperties(props);
+}

+ 22 - 0
src/dialog/SDL_dialog.h

@@ -0,0 +1,22 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+  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.
+*/
+
+void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props);

+ 26 - 13
src/dialog/android/SDL_androiddialog.c

@@ -20,26 +20,39 @@
 */
 
 #include "SDL_internal.h"
+#include "../SDL_dialog.h"
 #include "../../core/android/SDL_android.h"
 
-void SDLCALL SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location, bool allow_many)
+void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
 {
-    if (!Android_JNI_OpenFileDialog(callback, userdata, filters, nfilters, false, allow_many)) {
-        // SDL_SetError is already called when it fails
+    SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
+    int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0);
+    bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
+    bool is_save;
+
+    if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) {
+        SDL_SetError("File dialog driver unsupported (don't set SDL_HINT_FILE_DIALOG_DRIVER)");
         callback(userdata, NULL, -1);
+        return;
     }
-}
 
-void SDLCALL SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location)
-{
-    if (!Android_JNI_OpenFileDialog(callback, userdata, filters, nfilters, true, false)) {
+    switch (type) {
+    case SDL_FILEDIALOG_OPENFILE:
+        is_save = false;
+        break;
+
+    case SDL_FILEDIALOG_SAVEFILE:
+        is_save = true;
+        break;
+
+    case SDL_FILEDIALOG_OPENFOLDER:
+        SDL_Unsupported();
+        callback(userdata, NULL, -1);
+        return;
+    };
+
+    if (!Android_JNI_OpenFileDialog(callback, userdata, filters, nfilters, is_save, allow_many)) {
         // SDL_SetError is already called when it fails
         callback(userdata, NULL, -1);
     }
 }
-
-void SDLCALL SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const char *default_location, bool allow_many)
-{
-    SDL_Unsupported();
-    callback(userdata, NULL, -1);
-}

+ 23 - 26
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.h"
 #include "../SDL_dialog_utils.h"
 
 #ifdef SDL_PLATFORM_MACOS
@@ -26,15 +27,16 @@
 #import <Cocoa/Cocoa.h>
 #import <UniformTypeIdentifiers/UTType.h>
 
-typedef enum
+void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
 {
-    FDT_SAVE,
-    FDT_OPEN,
-    FDT_OPENFOLDER
-} cocoa_FileDialogType;
+    SDL_Window* window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL);
+    SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
+    int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0);
+    bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
+    const char* default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL);
+    const char* title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL);
+    const char* accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL);
 
-void show_file_dialog(cocoa_FileDialogType type, SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location, bool allow_many)
-{
     if (filters) {
         const char *msg = validate_filters(filters, nfilters);
 
@@ -46,7 +48,7 @@ void show_file_dialog(cocoa_FileDialogType type, SDL_DialogFileCallback callback
     }
 
     if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) {
-        SDL_SetError("File dialog driver unsupported");
+        SDL_SetError("File dialog driver unsupported (don't set SDL_HINT_FILE_DIALOG_DRIVER)");
         callback(userdata, NULL, -1);
         return;
     }
@@ -56,15 +58,17 @@ void show_file_dialog(cocoa_FileDialogType type, SDL_DialogFileCallback callback
     NSOpenPanel *dialog_as_open;
 
     switch (type) {
-    case FDT_SAVE:
+    case SDL_FILEDIALOG_SAVEFILE:
         dialog = [NSSavePanel savePanel];
         break;
-    case FDT_OPEN:
+
+    case SDL_FILEDIALOG_OPENFILE:
         dialog_as_open = [NSOpenPanel openPanel];
         [dialog_as_open setAllowsMultipleSelection:((allow_many == true) ? YES : NO)];
         dialog = dialog_as_open;
         break;
-    case FDT_OPENFOLDER:
+
+    case SDL_FILEDIALOG_OPENFOLDER:
         dialog_as_open = [NSOpenPanel openPanel];
         [dialog_as_open setCanChooseFiles:NO];
         [dialog_as_open setCanChooseDirectories:YES];
@@ -73,6 +77,14 @@ void show_file_dialog(cocoa_FileDialogType type, SDL_DialogFileCallback callback
         break;
     };
 
+    if (title) {
+        [dialog setTitle:[NSString stringWithUTF8String:title]];
+    }
+
+    if (accept) {
+        [dialog setPrompt:[NSString stringWithUTF8String:accept]];
+    }
+
     if (filters) {
         // On macOS 11.0 and up, this is an array of UTType. Prior to that, it's an array of NSString
         NSMutableArray *types = [[NSMutableArray alloc] initWithCapacity:nfilters ];
@@ -175,19 +187,4 @@ void show_file_dialog(cocoa_FileDialogType type, SDL_DialogFileCallback callback
     }
 }
 
-void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location, bool allow_many)
-{
-    show_file_dialog(FDT_OPEN, callback, userdata, window, filters, nfilters, default_location, allow_many);
-}
-
-void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location)
-{
-    show_file_dialog(FDT_SAVE, callback, userdata, window, filters, nfilters, default_location, 0);
-}
-
-void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, bool allow_many)
-{
-    show_file_dialog(FDT_OPENFOLDER, callback, userdata, window, NULL, 0, default_location, allow_many);
-}
-
 #endif // SDL_PLATFORM_MACOS

+ 3 - 13
src/dialog/dummy/SDL_dummydialog.c

@@ -20,21 +20,11 @@
 */
 #include "SDL_internal.h"
 
-#ifdef SDL_DIALOG_DUMMY
-
-void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location, bool allow_many)
-{
-  SDL_Unsupported();
-  callback(userdata, NULL, -1);
-}
+#include "../SDL_dialog.h"
 
-void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location)
-{
-  SDL_Unsupported();
-  callback(userdata, NULL, -1);
-}
+#ifdef SDL_DIALOG_DUMMY
 
-void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, bool allow_many)
+void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
 {
   SDL_Unsupported();
   callback(userdata, NULL, -1);

+ 50 - 16
src/dialog/haiku/SDL_haikudialog.cc

@@ -20,9 +20,11 @@
 */
 #include "SDL_internal.h"
 extern "C" {
+#include "../SDL_dialog.h"
 #include "../SDL_dialog_utils.h"
 }
 #include "../../core/haiku/SDL_BeApp.h"
+#include "../../video/haiku/SDL_BWin.h"
 
 #include <string>
 #include <vector>
@@ -190,8 +192,35 @@ private:
     SDLBRefFilter *m_filter;
 };
 
-void ShowDialog(bool save, SDL_DialogFileCallback callback, void *userdata, bool many, bool modal, const SDL_DialogFileFilter *filters, int nfilters, bool folder, const char *location)
+void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
 {
+    SDL_Window* window = (SDL_Window*) SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL);
+    SDL_DialogFileFilter* filters = (SDL_DialogFileFilter*) SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
+    int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0);
+    bool many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
+    const char* location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL);
+    const char* title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL);
+    const char* accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL);
+    const char* cancel = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_CANCEL_STRING, NULL);
+
+    bool modal = !!window;
+
+    bool save = false;
+    bool folder = false;
+
+    switch (type) {
+    case SDL_FILEDIALOG_SAVEFILE:
+        save = true;
+        break;
+
+    case SDL_FILEDIALOG_OPENFILE:
+        break;
+
+    case SDL_FILEDIALOG_OPENFOLDER:
+        folder = true;
+        break;
+    };
+
     if (!SDL_InitBeApp()) {
         char* err = SDL_strdup(SDL_GetError());
         SDL_SetError("Couldn't init Be app: %s", err);
@@ -238,22 +267,27 @@ void ShowDialog(bool save, SDL_DialogFileCallback callback, void *userdata, bool
     }
 
     BFilePanel *panel = new BFilePanel(save ? B_SAVE_PANEL : B_OPEN_PANEL, messenger, location ? &entryref : NULL, folder ? B_DIRECTORY_NODE : B_FILE_NODE, many, NULL, filter, modal);
-    looper->SetToBeFreed(messenger, panel, filter);
-    looper->Run();
-    panel->Show();
-}
 
-void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location, bool allow_many)
-{
-    ShowDialog(false, callback, userdata, allow_many == true, !!window, filters, nfilters, false, default_location);
-}
+    if (title) {
+        panel->Window()->SetTitle(title);
+    }
 
-void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location)
-{
-    ShowDialog(true, callback, userdata, false, !!window, filters, nfilters, false, default_location);
-}
+    if (accept) {
+        panel->SetButtonLabel(B_DEFAULT_BUTTON, accept);
+    }
 
-void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const char* default_location, bool allow_many)
-{
-    ShowDialog(false, callback, userdata, allow_many == true, !!window, NULL, 0, true, default_location);
+    if (cancel) {
+        panel->SetButtonLabel(B_CANCEL_BUTTON, cancel);
+    }
+
+    if (window) {
+        SDL_BWin *bwin = (SDL_BWin *)(window->internal);
+        panel->Window()->SetLook(B_MODAL_WINDOW_LOOK);
+        panel->Window()->SetFeel(B_MODAL_SUBSET_WINDOW_FEEL);
+        panel->Window()->AddToSubset(bwin);
+    }
+
+    looper->SetToBeFreed(messenger, panel, filter);
+    looper->Run();
+    panel->Show();
 }

+ 45 - 34
src/dialog/unix/SDL_portaldialog.c

@@ -275,8 +275,43 @@ not_our_signal:
     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 }
 
-static void DBus_OpenDialog(const char *method, const char *method_title, SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location, bool allow_many, int open_folders)
+void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
 {
+    const char *method;
+    const char *method_title;
+
+    SDL_Window* window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL);
+    SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
+    int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0);
+    bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
+    const char* default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL);
+    const char* accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL);
+    bool open_folders = false;
+
+    switch (type) {
+    case SDL_FILEDIALOG_OPENFILE:
+        method = "OpenFile";
+        method_title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, "Open File");
+        break;
+
+    case SDL_FILEDIALOG_SAVEFILE:
+        method = "SaveFile";
+        method_title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, "Save File");
+        break;
+
+    case SDL_FILEDIALOG_OPENFOLDER:
+        method = "OpenFile";
+        method_title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, "Open Folder");
+        open_folders = true;
+        break;
+
+    default:
+        /* This is already checked in ../SDL_dialog.c; this silences compiler warnings */
+        SDL_SetError("Invalid file dialog type: %d", type);
+        callback(userdata, NULL, -1);
+        return;
+    }
+
     SDL_DBusContext *dbus = SDL_DBus_GetContext();
     DBusMessage *msg;
     DBusMessageIter params, options;
@@ -285,7 +320,7 @@ static void DBus_OpenDialog(const char *method, const char *method_title, SDL_Di
     int filter_len;
     static uint32_t handle_id = 0;
     static char *default_parent_window = "";
-    SDL_PropertiesID props = SDL_GetWindowProperties(window);
+    SDL_PropertiesID window_props = SDL_GetWindowProperties(window);
 
     const char *err_msg = validate_filters(filters, nfilters);
 
@@ -311,8 +346,8 @@ static void DBus_OpenDialog(const char *method, const char *method_title, SDL_Di
     dbus->message_iter_init_append(msg, &params);
 
     handle_str = default_parent_window;
-    if (props) {
-        const char *parent_handle = SDL_GetStringProperty(props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_EXPORT_HANDLE_STRING, NULL);
+    if (window_props) {
+        const char *parent_handle = SDL_GetStringProperty(window_props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_EXPORT_HANDLE_STRING, NULL);
         if (parent_handle) {
             size_t len = SDL_strlen(parent_handle);
             len += sizeof(WAYLAND_HANDLE_PREFIX) + 1;
@@ -324,7 +359,7 @@ static void DBus_OpenDialog(const char *method, const char *method_title, SDL_Di
 
             SDL_snprintf(handle_str, len, "%s%s", WAYLAND_HANDLE_PREFIX, parent_handle);
         } else {
-            const Uint64 xid = (Uint64)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
+            const Uint64 xid = (Uint64)SDL_GetNumberProperty(window_props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
             if (xid) {
                 const size_t len = sizeof(X11_HANDLE_PREFIX) + 24; // A 64-bit number can be 20 characters max.
                 handle_str = SDL_malloc(len * sizeof(char));
@@ -357,7 +392,7 @@ static void DBus_OpenDialog(const char *method, const char *method_title, SDL_Di
     SDL_free(handle_str);
 
     DBus_AppendBoolOption(dbus, &options, "modal", !!window);
-    if (allow_many == true) {
+    if (allow_many) {
         DBus_AppendBoolOption(dbus, &options, "multiple", 1);
     }
     if (open_folders) {
@@ -369,6 +404,9 @@ static void DBus_OpenDialog(const char *method, const char *method_title, SDL_Di
     if (default_location) {
         DBus_AppendByteArray(dbus, &options, "current_folder", default_location);
     }
+    if (accept) {
+        DBus_AppendStringOption(dbus, &options, "accept_label", accept);
+    }
     dbus->message_iter_close_container(&params, &options);
 
     DBusMessage *reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_INFINITE, NULL);
@@ -425,21 +463,6 @@ incorrect_type:
     dbus->message_unref(reply);
 }
 
-void SDL_Portal_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location, bool allow_many)
-{
-    DBus_OpenDialog("OpenFile", "Open File", callback, userdata, window, filters, nfilters, default_location, allow_many, 0);
-}
-
-void SDL_Portal_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location)
-{
-    DBus_OpenDialog("SaveFile", "Save File", callback, userdata, window, filters, nfilters, default_location, 0, 0);
-}
-
-void SDL_Portal_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, bool allow_many)
-{
-    DBus_OpenDialog("OpenFile", "Open Folder", callback, userdata, window, NULL, 0, default_location, allow_many, 1);
-}
-
 bool SDL_Portal_detect(void)
 {
     SDL_DBusContext *dbus = SDL_DBus_GetContext();
@@ -500,19 +523,7 @@ done:
 
 // Dummy implementation to avoid compilation problems
 
-void SDL_Portal_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location, bool allow_many)
-{
-    SDL_Unsupported();
-    callback(userdata, NULL, -1);
-}
-
-void SDL_Portal_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location)
-{
-    SDL_Unsupported();
-    callback(userdata, NULL, -1);
-}
-
-void SDL_Portal_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, bool allow_many)
+void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
 {
     SDL_Unsupported();
     callback(userdata, NULL, -1);

+ 1 - 3
src/dialog/unix/SDL_portaldialog.h

@@ -21,9 +21,7 @@
 
 #include "SDL_internal.h"
 
-void SDL_Portal_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location, bool allow_many);
-void SDL_Portal_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location);
-void SDL_Portal_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, bool allow_many);
+void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props);
 
 /** @returns non-zero if available, zero if unavailable */
 bool SDL_Portal_detect(void);

+ 11 - 40
src/dialog/unix/SDL_unixdialog.c

@@ -20,19 +20,13 @@
 */
 #include "SDL_internal.h"
 
+#include "../SDL_dialog.h"
 #include "./SDL_portaldialog.h"
 #include "./SDL_zenitydialog.h"
 
-static void (*detected_open)(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location, bool allow_many) = NULL;
-static void (*detected_save)(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location) = NULL;
-static void (*detected_folder)(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, bool allow_many) = NULL;
+static void (*detected_function)(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) = NULL;
 
-static int detect_available_methods(const char *value);
-
-void SDLCALL hint_callback(void *userdata, const char *name, const char *oldValue, const char *newValue)
-{
-    detect_available_methods(newValue);
-}
+void SDLCALL hint_callback(void *userdata, const char *name, const char *oldValue, const char *newValue);
 
 static void set_callback(void)
 {
@@ -53,58 +47,35 @@ static int detect_available_methods(const char *value)
 
     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;
+            detected_function = SDL_Portal_ShowFileDialogWithProperties;
             return 1;
         }
     }
 
     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;
+            detected_function = SDL_Zenity_ShowFileDialogWithProperties;
             return 2;
         }
     }
 
-    SDL_SetError("File dialog driver unsupported");
+    SDL_SetError("File dialog driver unsupported (supported values for SDL_HINT_FILE_DIALOG_DRIVER are 'zenity' and 'portal')");
     return 0;
 }
 
-void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location, bool allow_many)
-{
-    // Call detect_available_methods() again each time in case the situation changed
-    if (!detected_open && !detect_available_methods(NULL)) {
-        // SetError() done by detect_available_methods()
-        callback(userdata, NULL, -1);
-        return;
-    }
-
-    detected_open(callback, userdata, window, filters, nfilters, default_location, allow_many);
-}
-
-void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location)
+void SDLCALL hint_callback(void *userdata, const char *name, const char *oldValue, const char *newValue)
 {
-    // Call detect_available_methods() again each time in case the situation changed
-    if (!detected_save && !detect_available_methods(NULL)) {
-        // SetError() done by detect_available_methods()
-        callback(userdata, NULL, -1);
-        return;
-    }
-
-    detected_save(callback, userdata, window, filters, nfilters, default_location);
+    detect_available_methods(newValue);
 }
 
-void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, bool allow_many)
+void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
 {
     // Call detect_available_methods() again each time in case the situation changed
-    if (!detected_folder && !detect_available_methods(NULL)) {
+    if (!detected_function && !detect_available_methods(NULL)) {
         // SetError() done by detect_available_methods()
         callback(userdata, NULL, -1);
         return;
     }
 
-    detected_folder(callback, userdata, window, default_location, allow_many);
+    detected_function(type, callback, userdata, props);
 }

+ 107 - 83
src/dialog/unix/SDL_zenitydialog.c

@@ -26,13 +26,6 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
-typedef enum
-{
-    ZENITY_MULTIPLE = 0x1,
-    ZENITY_DIRECTORY = 0x2,
-    ZENITY_SAVE = 0x4
-} zenityFlags;
-
 typedef struct
 {
     SDL_DialogFileCallback callback;
@@ -40,7 +33,13 @@ typedef struct
     const char* filename;
     const SDL_DialogFileFilter *filters;
     int nfilters;
-    Uint32 flags;
+    bool allow_many;
+    SDL_FileDialogType type;
+    /* Zenity only works with X11 handles apparently */
+    Uint64 x11_window_handle;
+    const char *title;
+    const char *accept;
+    const char *cancel;
 } zenityArgs;
 
 #define CLEAR_AND_RETURN()                                                    \
@@ -84,7 +83,10 @@ char *zenity_clean_name(const char *name)
 /* Exec call format:
  *
  *     /usr/bin/env zenity --file-selection --separator=\n [--multiple]
- *                         [--directory] [--save] [--filename FILENAME]
+ *                         [--directory] [--save --confirm-overwrite]
+ *                         [--filename FILENAME] [--modal --attach 0x11w1nd0w]
+ *                         [--title TITLE] [--ok-label ACCEPT]
+ *                         [--cancel-label CANCEL]
  *                         [--file-filter=Filter Name | *.filt *.fn ...]...
  */
 static char** generate_args(const zenityArgs* info)
@@ -94,19 +96,40 @@ static char** generate_args(const zenityArgs* info)
     char **argv = NULL;
 
     // ARGC PASS
-    if (info->flags & ZENITY_MULTIPLE) {
+    if (info->allow_many) {
         argc++;
     }
 
-    if (info->flags & ZENITY_DIRECTORY) {
+    switch (info->type) {
+    case SDL_FILEDIALOG_OPENFILE:
+        break;
+
+    case SDL_FILEDIALOG_SAVEFILE:
+        argc += 2;
+        break;
+
+    case SDL_FILEDIALOG_OPENFOLDER:
         argc++;
+        break;
+    };
+
+    if (info->filename) {
+        argc += 2;
     }
 
-    if (info->flags & ZENITY_SAVE) {
-        argc++;
+    if (info->x11_window_handle) {
+        argc += 3;
     }
 
-    if (info->filename) {
+    if (info->title) {
+        argc += 2;
+    }
+
+    if (info->accept) {
+        argc += 2;
+    }
+
+    if (info->cancel) {
         argc += 2;
     }
 
@@ -119,6 +142,7 @@ static char** generate_args(const zenityArgs* info)
         return NULL;
     }
 
+    // ARGV PASS
     argv[nextarg++] = SDL_strdup("/usr/bin/env");
     CHECK_OOM()
     argv[nextarg++] = SDL_strdup("zenity");
@@ -128,27 +152,68 @@ static char** generate_args(const zenityArgs* info)
     argv[nextarg++] = SDL_strdup("--separator=\n");
     CHECK_OOM()
 
-    // ARGV PASS
-    if (info->flags & ZENITY_MULTIPLE) {
+    if (info->allow_many) {
         argv[nextarg++] = SDL_strdup("--multiple");
         CHECK_OOM()
     }
 
-    if (info->flags & ZENITY_DIRECTORY) {
+    switch (info->type) {
+    case SDL_FILEDIALOG_OPENFILE:
+        break;
+
+    case SDL_FILEDIALOG_SAVEFILE:
+        argv[nextarg++] = SDL_strdup("--save");
+        /* Asking before overwriting while saving seems like a sane default */
+        argv[nextarg++] = SDL_strdup("--confirm-overwrite");
+        break;
+
+    case SDL_FILEDIALOG_OPENFOLDER:
         argv[nextarg++] = SDL_strdup("--directory");
+        break;
+    };
+
+    if (info->filename) {
+        argv[nextarg++] = SDL_strdup("--filename");
+        CHECK_OOM()
+
+        argv[nextarg++] = SDL_strdup(info->filename);
         CHECK_OOM()
     }
 
-    if (info->flags & ZENITY_SAVE) {
-        argv[nextarg++] = SDL_strdup("--save");
+    if (info->x11_window_handle) {
+        argv[nextarg++] = SDL_strdup("--modal");
+        CHECK_OOM()
+
+        argv[nextarg++] = SDL_strdup("--attach");
+        CHECK_OOM()
+
+        argv[nextarg++] = SDL_malloc(64);
         CHECK_OOM()
+
+        SDL_snprintf(argv[nextarg - 1], 64, "0x%" SDL_PRIx64, info->x11_window_handle);
     }
 
-    if (info->filename) {
-        argv[nextarg++] = SDL_strdup("--filename");
+    if (info->title) {
+        argv[nextarg++] = SDL_strdup("--title");
         CHECK_OOM()
 
-        argv[nextarg++] = SDL_strdup(info->filename);
+        argv[nextarg++] = SDL_strdup(info->title);
+        CHECK_OOM()
+    }
+
+    if (info->accept) {
+        argv[nextarg++] = SDL_strdup("--ok-label");
+        CHECK_OOM()
+
+        argv[nextarg++] = SDL_strdup(info->accept);
+        CHECK_OOM()
+    }
+
+    if (info->cancel) {
+        argv[nextarg++] = SDL_strdup("--cancel-label");
+        CHECK_OOM()
+
+        argv[nextarg++] = SDL_strdup(info->cancel);
         CHECK_OOM()
     }
 
@@ -290,7 +355,7 @@ static int run_zenity_thread(void* ptr)
     return 0;
 }
 
-void SDL_Zenity_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location, bool allow_many)
+void SDL_Zenity_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
 {
     zenityArgs *args;
     SDL_Thread *thread;
@@ -301,72 +366,31 @@ void SDL_Zenity_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userda
         return;
     }
 
+    /* Properties can be destroyed as soon as the function returns; copy over what we need. */
     args->callback = callback;
     args->userdata = userdata;
-    args->filename = default_location;
-    args->filters = filters;
-    args->nfilters = nfilters;
-    args->flags = (allow_many == true) ? ZENITY_MULTIPLE : 0;
-
-    thread = SDL_CreateThread(run_zenity_thread, "SDL_ShowOpenFileDialog", (void *) args);
-
-    if (thread == NULL) {
-        callback(userdata, NULL, -1);
-        return;
-    }
-
-    SDL_DetachThread(thread);
-}
-
-void SDL_Zenity_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location)
-{
-    zenityArgs *args;
-    SDL_Thread *thread;
-
-    args = SDL_malloc(sizeof(zenityArgs));
-    if (args == NULL) {
-        callback(userdata, NULL, -1);
-        return;
-    }
-
-    args->callback = callback;
-    args->userdata = userdata;
-    args->filename = default_location;
-    args->filters = filters;
-    args->nfilters = nfilters;
-    args->flags = ZENITY_SAVE;
-
-    thread = SDL_CreateThread(run_zenity_thread, "SDL_ShowSaveFileDialog", (void *) args);
-
-    if (thread == NULL) {
-        callback(userdata, NULL, -1);
-        return;
-    }
-
-    SDL_DetachThread(thread);
-}
-
-void SDL_Zenity_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, bool allow_many)
-{
-    zenityArgs *args;
-    SDL_Thread *thread;
-
-    args = SDL_malloc(sizeof(zenityArgs));
-    if (args == NULL) {
-        callback(userdata, NULL, -1);
-        return;
+    args->filename = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL);
+    args->filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
+    args->nfilters = SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0);
+    args->allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
+    args->type = type;
+    args->x11_window_handle = 0;
+    args->title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL);
+    args->accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL);
+    args->cancel = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_CANCEL_STRING, NULL);
+
+    SDL_Window *window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL);
+    if (window) {
+        SDL_PropertiesID window_props = SDL_GetWindowProperties(window);
+        if (window_props) {
+            args->x11_window_handle = (Uint64) SDL_GetNumberProperty(window_props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
+        }
     }
 
-    args->callback = callback;
-    args->userdata = userdata;
-    args->filename = default_location;
-    args->filters = NULL;
-    args->nfilters = 0;
-    args->flags = ((allow_many == true) ? ZENITY_MULTIPLE : 0) | ZENITY_DIRECTORY;
-
-    thread = SDL_CreateThread(run_zenity_thread, "SDL_ShowOpenFolderDialog", (void *) args);
+    thread = SDL_CreateThread(run_zenity_thread, "SDL_ZenityFileDialog", (void *) args);
 
     if (thread == NULL) {
+        SDL_free(args);
         callback(userdata, NULL, -1);
         return;
     }

+ 1 - 3
src/dialog/unix/SDL_zenitydialog.h

@@ -21,9 +21,7 @@
 
 #include "SDL_internal.h"
 
-extern void SDL_Zenity_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location, bool allow_many);
-extern void SDL_Zenity_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location);
-extern void SDL_Zenity_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, bool allow_many);
+extern void SDL_Zenity_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props);
 
 /** @returns non-zero if available, zero if unavailable */
 extern bool SDL_Zenity_detect(void);

+ 110 - 48
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.h"
 #include "../SDL_dialog_utils.h"
 
 #include <windows.h>
@@ -32,7 +33,7 @@
 
 typedef struct
 {
-    int is_save;
+    bool is_save;
     const SDL_DialogFileFilter *filters;
     int nfilters;
     const char* default_file;
@@ -40,6 +41,9 @@ typedef struct
     DWORD flags;
     SDL_DialogFileCallback callback;
     void* userdata;
+    const char* title;
+    const char* accept;
+    const char* cancel;
 } winArgs;
 
 typedef struct
@@ -48,6 +52,9 @@ typedef struct
     SDL_DialogFileCallback callback;
     const char* default_folder;
     void* userdata;
+    const char* title;
+    const char* accept;
+    const char* cancel;
 } winFArgs;
 
 /** Converts dialog.nFilterIndex to SDL-compatible value */
@@ -60,7 +67,7 @@ int getFilterIndex(int as_reported_by_windows)
 void windows_ShowFileDialog(void *ptr)
 {
     winArgs *args = (winArgs *) ptr;
-    int is_save = args->is_save;
+    bool is_save = args->is_save;
     const SDL_DialogFileFilter *filters = args->filters;
     int nfilters = args->nfilters;
     const char* default_file = args->default_file;
@@ -68,6 +75,7 @@ void windows_ShowFileDialog(void *ptr)
     DWORD flags = args->flags;
     SDL_DialogFileCallback callback = args->callback;
     void* userdata = args->userdata;
+    const char *title = args->title;
 
     /* GetOpenFileName and GetSaveFileName have the same signature
        (yes, LPOPENFILENAMEW even for the save dialog) */
@@ -185,6 +193,34 @@ void windows_ShowFileDialog(void *ptr)
         SDL_free(filterlist);
     }
 
+    wchar_t *title_w = NULL;
+
+    if (title) {
+        int title_len = (int) SDL_strlen(title);
+
+        /* If the title is longer than 2GB, it might be exploitable. */
+        if (title_len < 0) {
+            title_len = 0;
+        }
+
+        int title_wlen = MultiByteToWideChar(CP_UTF8, 0, title, title_len, NULL, 0);
+
+        if (title_wlen < 0) {
+            title_wlen = 0;
+        }
+
+        title_w = (wchar_t *)SDL_malloc(title_wlen * sizeof(wchar_t));
+
+        if (!title_w) {
+            SDL_free(filter_wchar);
+            SDL_free(filebuffer);
+            callback(userdata, NULL, -1);
+            return;
+        }
+
+        MultiByteToWideChar(CP_UTF8, 0, title, title_len, title_w, title_wlen);
+    }
+
     OPENFILENAMEW dialog;
     dialog.lStructSize = sizeof(OPENFILENAME);
     dialog.hwndOwner = window;
@@ -197,7 +233,7 @@ void windows_ShowFileDialog(void *ptr)
     dialog.nMaxFile = SELECTLIST_SIZE;
     dialog.lpstrFileTitle = NULL;
     dialog.lpstrInitialDir = *initfolder ? initfolder : NULL;
-    dialog.lpstrTitle = NULL;
+    dialog.lpstrTitle = title_w;
     dialog.Flags = flags | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_NOCHANGEDIR;
     dialog.nFileOffset = 0;
     dialog.nFileExtension = 0;
@@ -211,6 +247,7 @@ void windows_ShowFileDialog(void *ptr)
     BOOL result = pGetAnyFileName(&dialog);
 
     SDL_free(filter_wchar);
+    SDL_free(title_w);
 
     if (result) {
         if (!(flags & OFN_ALLOWMULTISELECT)) {
@@ -388,25 +425,54 @@ void windows_ShowFolderDialog(void* ptr)
     SDL_DialogFileCallback callback = args->callback;
     void *userdata = args->userdata;
     HWND parent = NULL;
+    const char *title = args->title;
 
     if (window) {
         parent = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
     }
 
+    wchar_t *title_w = NULL;
+
+    if (title) {
+        int title_len = (int) SDL_strlen(title);
+
+        /* If the title is longer than 2GB, it might be exploitable. */
+        if (title_len < 0) {
+            title_len = 0;
+        }
+
+        int title_wlen = MultiByteToWideChar(CP_UTF8, 0, title, title_len, NULL, 0);
+
+        if (title_wlen < 0) {
+            title_wlen = 0;
+        }
+
+        title_w = (wchar_t *)SDL_malloc(title_wlen * sizeof(wchar_t));
+
+        if (!title_w) {
+            callback(userdata, NULL, -1);
+            return;
+        }
+
+        MultiByteToWideChar(CP_UTF8, 0, title, title_len, title_w, title_wlen);
+    }
+
     wchar_t buffer[MAX_PATH];
 
     BROWSEINFOW dialog;
     dialog.hwndOwner = parent;
     dialog.pidlRoot = NULL;
-    // Windows docs say this is `LPTSTR` - apparently it's actually `LPWSTR`
     dialog.pszDisplayName = buffer;
-    dialog.lpszTitle = NULL;
+    dialog.lpszTitle = title_w;
     dialog.ulFlags = BIF_USENEWUI;
     dialog.lpfn = browse_callback_proc;
     dialog.lParam = (LPARAM)args->default_folder;
     dialog.iImage = 0;
 
     LPITEMIDLIST lpItem = SHBrowseForFolderW(&dialog);
+
+    SDL_free(title_w);
+
     if (lpItem != NULL) {
         SHGetPathFromIDListW(lpItem, buffer);
         char *chosen_file = WIN_StringToUTF8W(buffer);
@@ -426,13 +492,12 @@ int windows_folder_dialog_thread(void* ptr)
     return 0;
 }
 
-void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location, bool allow_many)
+static void ShowFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location, bool allow_many, bool is_save, const char* title, const char* accept, const char* cancel)
 {
     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;
@@ -444,16 +509,19 @@ void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL
         return;
     }
 
-    args->is_save = 0;
+    args->is_save = is_save;
     args->filters = filters;
     args->nfilters = nfilters;
     args->default_file = default_location;
     args->parent = window;
-    args->flags = (allow_many != false) ? OFN_ALLOWMULTISELECT : 0;
+    args->flags = allow_many ? OFN_ALLOWMULTISELECT : 0;
     args->callback = callback;
     args->userdata = userdata;
+    args->title = title;
+    args->accept = accept;
+    args->cancel = cancel;
 
-    thread = SDL_CreateThread(windows_file_dialog_thread, "SDL_ShowOpenFileDialog", (void *) args);
+    thread = SDL_CreateThread(windows_file_dialog_thread, "SDL_Windows_ShowFileDialog", (void *) args);
 
     if (thread == NULL) {
         callback(userdata, NULL, -1);
@@ -464,9 +532,9 @@ void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL
     SDL_DetachThread(thread);
 }
 
-void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location)
+void ShowFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, bool allow_many, const char* title, const char* accept, const char* cancel)
 {
-    winArgs *args;
+    winFArgs *args;
     SDL_Thread *thread;
 
     if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) {
@@ -475,22 +543,21 @@ void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL
         return;
     }
 
-    args = (winArgs *)SDL_malloc(sizeof(*args));
+    args = (winFArgs *)SDL_malloc(sizeof(*args));
     if (args == NULL) {
         callback(userdata, NULL, -1);
         return;
     }
 
-    args->is_save = 1;
-    args->filters = filters;
-    args->nfilters = nfilters;
-    args->default_file = default_location;
     args->parent = window;
-    args->flags = 0;
     args->callback = callback;
+    args->default_folder = default_location;
     args->userdata = userdata;
+    args->title = title;
+    args->accept = accept;
+    args->cancel = cancel;
 
-    thread = SDL_CreateThread(windows_file_dialog_thread, "SDL_ShowSaveFileDialog", (void *) args);
+    thread = SDL_CreateThread(windows_folder_dialog_thread, "SDL_Windows_ShowFolderDialog", (void *) args);
 
     if (thread == NULL) {
         callback(userdata, NULL, -1);
@@ -501,35 +568,30 @@ void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL
     SDL_DetachThread(thread);
 }
 
-void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, bool allow_many)
+void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
 {
-    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 = (winFArgs *)SDL_malloc(sizeof(*args));
-    if (args == NULL) {
-        callback(userdata, NULL, -1);
-        return;
-    }
-
-    args->parent = window;
-    args->callback = callback;
-    args->default_folder = default_location;
-    args->userdata = userdata;
-
-    thread = SDL_CreateThread(windows_folder_dialog_thread, "SDL_ShowOpenFolderDialog", (void *) args);
-
-    if (thread == NULL) {
-        callback(userdata, NULL, -1);
-        SDL_free(args);
-        return;
-    }
+    /* The internal functions will start threads, and the properties may be freed as soon as this function returns.
+       Save a copy of what we need before invoking the functions and starting the threads. */
+    SDL_Window* window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL);
+    SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
+    int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0);
+    bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
+    const char* default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL);
+    const char* title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL);
+    const char* accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL);
+    const char* cancel = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_CANCEL_STRING, NULL);
+    bool is_save = false;
+
+    switch (type) {
+    case SDL_FILEDIALOG_SAVEFILE:
+        is_save = true;
+        SDL_FALLTHROUGH;
+    case SDL_FILEDIALOG_OPENFILE:
+        ShowFileDialog(callback, userdata, window, filters, nfilters, default_location, allow_many, is_save, title, accept, cancel);
+        break;
 
-    SDL_DetachThread(thread);
+    case SDL_FILEDIALOG_OPENFOLDER:
+        ShowFolderDialog(callback, userdata, window, default_location, allow_many, title, accept, cancel);
+        break;
+    };
 }