summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/dialog
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/dialog')
-rw-r--r--contrib/SDL-3.2.8/src/dialog/SDL_dialog.c131
-rw-r--r--contrib/SDL-3.2.8/src/dialog/SDL_dialog.h22
-rw-r--r--contrib/SDL-3.2.8/src/dialog/SDL_dialog_utils.c256
-rw-r--r--contrib/SDL-3.2.8/src/dialog/SDL_dialog_utils.h59
-rw-r--r--contrib/SDL-3.2.8/src/dialog/android/SDL_androiddialog.c58
-rw-r--r--contrib/SDL-3.2.8/src/dialog/cocoa/SDL_cocoadialog.m188
-rw-r--r--contrib/SDL-3.2.8/src/dialog/dummy/SDL_dummydialog.c33
-rw-r--r--contrib/SDL-3.2.8/src/dialog/haiku/SDL_haikudialog.cc293
-rw-r--r--contrib/SDL-3.2.8/src/dialog/unix/SDL_portaldialog.c545
-rw-r--r--contrib/SDL-3.2.8/src/dialog/unix/SDL_portaldialog.h27
-rw-r--r--contrib/SDL-3.2.8/src/dialog/unix/SDL_unixdialog.c81
-rw-r--r--contrib/SDL-3.2.8/src/dialog/unix/SDL_zenitydialog.c366
-rw-r--r--contrib/SDL-3.2.8/src/dialog/unix/SDL_zenitydialog.h27
-rw-r--r--contrib/SDL-3.2.8/src/dialog/windows/SDL_windowsdialog.c611
14 files changed, 2697 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/dialog/SDL_dialog.c b/contrib/SDL-3.2.8/src/dialog/SDL_dialog.c
new file mode 100644
index 0000000..a77e443
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/dialog/SDL_dialog.c
@@ -0,0 +1,131 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "SDL_dialog.h"
24#include "SDL_dialog_utils.h"
25
26void SDL_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
27{
28 if (!callback) {
29 return;
30 }
31#ifdef SDL_DIALOG_DISABLED
32 SDL_SetError("SDL not built with dialog support");
33 callback(userdata, NULL, -1);
34#else
35 SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
36 int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, -1);
37
38 if (filters && nfilters == -1) {
39 SDL_SetError("Set filter pointers, but didn't set number of filters (SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER)");
40 callback(userdata, NULL, -1);
41 return;
42 }
43
44 const char *msg = validate_filters(filters, nfilters);
45
46 if (msg) {
47 SDL_SetError("Invalid dialog file filters: %s", msg);
48 callback(userdata, NULL, -1);
49 return;
50 }
51
52 switch (type) {
53 case SDL_FILEDIALOG_OPENFILE:
54 case SDL_FILEDIALOG_SAVEFILE:
55 case SDL_FILEDIALOG_OPENFOLDER:
56 SDL_SYS_ShowFileDialogWithProperties(type, callback, userdata, props);
57 break;
58
59 default:
60 SDL_SetError("Unsupported file dialog type: %d", (int) type);
61 callback(userdata, NULL, -1);
62 break;
63 };
64#endif
65}
66
67void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location, bool allow_many)
68{
69#ifdef SDL_DIALOG_DISABLED
70 if (!callback) {
71 return;
72 }
73 SDL_SetError("SDL not built with dialog support");
74 callback(userdata, NULL, -1);
75#else
76 SDL_PropertiesID props = SDL_CreateProperties();
77
78 SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, (void *) filters);
79 SDL_SetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, nfilters);
80 SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, window);
81 SDL_SetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, default_location);
82 SDL_SetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, allow_many);
83
84 SDL_ShowFileDialogWithProperties(SDL_FILEDIALOG_OPENFILE, callback, userdata, props);
85
86 SDL_DestroyProperties(props);
87#endif
88}
89
90void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location)
91{
92#ifdef SDL_DIALOG_DISABLED
93 if (!callback) {
94 return;
95 }
96 SDL_SetError("SDL not built with dialog support");
97 callback(userdata, NULL, -1);
98#else
99 SDL_PropertiesID props = SDL_CreateProperties();
100
101 SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, (void *) filters);
102 SDL_SetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, nfilters);
103 SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, window);
104 SDL_SetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, default_location);
105
106 SDL_ShowFileDialogWithProperties(SDL_FILEDIALOG_SAVEFILE, callback, userdata, props);
107
108 SDL_DestroyProperties(props);
109#endif
110}
111
112void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const char *default_location, bool allow_many)
113{
114#ifdef SDL_DIALOG_DISABLED
115 if (!callback) {
116 return;
117 }
118 SDL_SetError("SDL not built with dialog support");
119 callback(userdata, NULL, -1);
120#else
121 SDL_PropertiesID props = SDL_CreateProperties();
122
123 SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, window);
124 SDL_SetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, default_location);
125 SDL_SetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, allow_many);
126
127 SDL_ShowFileDialogWithProperties(SDL_FILEDIALOG_OPENFOLDER, callback, userdata, props);
128
129 SDL_DestroyProperties(props);
130#endif
131}
diff --git a/contrib/SDL-3.2.8/src/dialog/SDL_dialog.h b/contrib/SDL-3.2.8/src/dialog/SDL_dialog.h
new file mode 100644
index 0000000..beee7dd
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/dialog/SDL_dialog.h
@@ -0,0 +1,22 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props);
diff --git a/contrib/SDL-3.2.8/src/dialog/SDL_dialog_utils.c b/contrib/SDL-3.2.8/src/dialog/SDL_dialog_utils.c
new file mode 100644
index 0000000..8d2b186
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/dialog/SDL_dialog_utils.c
@@ -0,0 +1,256 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "SDL_dialog_utils.h"
24
25char *convert_filters(const SDL_DialogFileFilter *filters, int nfilters,
26 NameTransform ntf, const char *prefix,
27 const char *separator, const char *suffix,
28 const char *filt_prefix, const char *filt_separator,
29 const char *filt_suffix, const char *ext_prefix,
30 const char *ext_separator, const char *ext_suffix)
31{
32 char *combined;
33 char *new_combined;
34 char *converted;
35 const char *terminator;
36 size_t new_length;
37 int i;
38
39 if (!filters) {
40 SDL_SetError("Called convert_filters() with NULL filters (SDL bug)");
41 return NULL;
42 }
43
44 combined = SDL_strdup(prefix);
45
46 if (!combined) {
47 return NULL;
48 }
49
50 for (i = 0; i < nfilters; i++) {
51 const SDL_DialogFileFilter *f = &filters[i];
52
53 converted = convert_filter(*f, ntf, filt_prefix, filt_separator,
54 filt_suffix, ext_prefix, ext_separator,
55 ext_suffix);
56
57 if (!converted) {
58 SDL_free(combined);
59 return NULL;
60 }
61
62 terminator = ((i + 1) < nfilters) ? separator : suffix;
63 new_length = SDL_strlen(combined) + SDL_strlen(converted)
64 + SDL_strlen(terminator) + 1;
65
66 new_combined = (char *)SDL_realloc(combined, new_length);
67
68 if (!new_combined) {
69 SDL_free(converted);
70 SDL_free(combined);
71 return NULL;
72 }
73
74 combined = new_combined;
75
76 SDL_strlcat(combined, converted, new_length);
77 SDL_strlcat(combined, terminator, new_length);
78 SDL_free(converted);
79 }
80
81 new_length = SDL_strlen(combined) + SDL_strlen(suffix) + 1;
82
83 new_combined = (char *)SDL_realloc(combined, new_length);
84
85 if (!new_combined) {
86 SDL_free(combined);
87 return NULL;
88 }
89
90 combined = new_combined;
91
92 SDL_strlcat(combined, suffix, new_length);
93
94 return combined;
95}
96
97char *convert_filter(SDL_DialogFileFilter filter, NameTransform ntf,
98 const char *prefix, const char *separator,
99 const char *suffix, const char *ext_prefix,
100 const char *ext_separator, const char *ext_suffix)
101{
102 char *converted;
103 char *name_filtered;
104 size_t total_length;
105 char *list;
106
107 list = convert_ext_list(filter.pattern, ext_prefix, ext_separator,
108 ext_suffix);
109
110 if (!list) {
111 return NULL;
112 }
113
114 if (ntf) {
115 name_filtered = ntf(filter.name);
116 } else {
117 // Useless strdup, but easier to read and maintain code this way
118 name_filtered = SDL_strdup(filter.name);
119 }
120
121 if (!name_filtered) {
122 SDL_free(list);
123 return NULL;
124 }
125
126 total_length = SDL_strlen(prefix) + SDL_strlen(name_filtered)
127 + SDL_strlen(separator) + SDL_strlen(list)
128 + SDL_strlen(suffix) + 1;
129
130 converted = (char *) SDL_malloc(total_length);
131
132 if (!converted) {
133 SDL_free(list);
134 SDL_free(name_filtered);
135 return NULL;
136 }
137
138 SDL_snprintf(converted, total_length, "%s%s%s%s%s", prefix, name_filtered,
139 separator, list, suffix);
140
141 SDL_free(list);
142 SDL_free(name_filtered);
143
144 return converted;
145}
146
147char *convert_ext_list(const char *list, const char *prefix,
148 const char *separator, const char *suffix)
149{
150 char *converted;
151 int semicolons;
152 size_t total_length;
153
154 semicolons = 0;
155
156 for (const char *c = list; *c; c++) {
157 semicolons += (*c == ';');
158 }
159
160 total_length =
161 SDL_strlen(list) - semicolons // length of list contents
162 + semicolons * SDL_strlen(separator) // length of separators
163 + SDL_strlen(prefix) + SDL_strlen(suffix) // length of prefix/suffix
164 + 1; // terminating null byte
165
166 converted = (char *) SDL_malloc(total_length);
167
168 if (!converted) {
169 return NULL;
170 }
171
172 *converted = '\0';
173
174 SDL_strlcat(converted, prefix, total_length);
175
176 /* Some platforms may prefer to handle the asterisk manually, but this
177 function offers to handle it for ease of use. */
178 if (SDL_strcmp(list, "*") == 0) {
179 SDL_strlcat(converted, "*", total_length);
180 } else {
181 for (const char *c = list; *c; c++) {
182 if ((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z')
183 || (*c >= '0' && *c <= '9') || *c == '-' || *c == '_'
184 || *c == '.') {
185 char str[2];
186 str[0] = *c;
187 str[1] = '\0';
188 SDL_strlcat(converted, str, total_length);
189 } else if (*c == ';') {
190 if (c == list || c[-1] == ';') {
191 SDL_SetError("Empty pattern not allowed");
192 SDL_free(converted);
193 return NULL;
194 }
195
196 SDL_strlcat(converted, separator, total_length);
197 } else {
198 SDL_SetError("Invalid character '%c' in pattern (Only [a-zA-Z0-9_.-] allowed, or a single *)", *c);
199 SDL_free(converted);
200 return NULL;
201 }
202 }
203 }
204
205 if (list[SDL_strlen(list) - 1] == ';') {
206 SDL_SetError("Empty pattern not allowed");
207 SDL_free(converted);
208 return NULL;
209 }
210
211 SDL_strlcat(converted, suffix, total_length);
212
213 return converted;
214}
215
216const char *validate_filters(const SDL_DialogFileFilter *filters, int nfilters)
217{
218 if (filters) {
219 for (int i = 0; i < nfilters; i++) {
220 const char *msg = validate_list(filters[i].pattern);
221
222 if (msg) {
223 return msg;
224 }
225 }
226 }
227
228 return NULL;
229}
230
231const char *validate_list(const char *list)
232{
233 if (SDL_strcmp(list, "*") == 0) {
234 return NULL;
235 } else {
236 for (const char *c = list; *c; c++) {
237 if ((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z')
238 || (*c >= '0' && *c <= '9') || *c == '-' || *c == '_'
239 || *c == '.') {
240 continue;
241 } else if (*c == ';') {
242 if (c == list || c[-1] == ';') {
243 return "Empty pattern not allowed";
244 }
245 } else {
246 return "Invalid character in pattern (Only [a-zA-Z0-9_.-] allowed, or a single *)";
247 }
248 }
249 }
250
251 if (list[SDL_strlen(list) - 1] == ';') {
252 return "Empty pattern not allowed";
253 }
254
255 return NULL;
256}
diff --git a/contrib/SDL-3.2.8/src/dialog/SDL_dialog_utils.h b/contrib/SDL-3.2.8/src/dialog/SDL_dialog_utils.h
new file mode 100644
index 0000000..1343dd7
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/dialog/SDL_dialog_utils.h
@@ -0,0 +1,59 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23/* The following are utility functions to help implementations.
24 They are ordered by scope largeness, decreasing. All implementations
25 should use them, as they check for invalid filters. Where they are unused,
26 the validate_* function further down below should be used. */
27
28/* Transform the name given in argument into something viable for the engine.
29 Useful if there are special characters to avoid on certain platforms (such
30 as "|" with Zenity). */
31typedef char *(NameTransform)(const char * name);
32
33// Converts all the filters into a single string.
34// <prefix>[filter]{<separator>[filter]...}<suffix>
35char *convert_filters(const SDL_DialogFileFilter *filters, int nfilters,
36 NameTransform ntf, const char *prefix,
37 const char *separator, const char *suffix,
38 const char *filt_prefix, const char *filt_separator,
39 const char *filt_suffix, const char *ext_prefix,
40 const char *ext_separator, const char *ext_suffix);
41
42// Converts one filter into a single string.
43// <prefix>[filter name]<separator>[filter extension list]<suffix>
44char *convert_filter(SDL_DialogFileFilter filter, NameTransform ntf,
45 const char *prefix, const char *separator,
46 const char *suffix, const char *ext_prefix,
47 const char *ext_separator, const char *ext_suffix);
48
49// Converts the extension list of a filter into a single string.
50// <prefix>[extension]{<separator>[extension]...}<suffix>
51char *convert_ext_list(const char *list, const char *prefix,
52 const char *separator, const char *suffix);
53
54/* Must be used if convert_* functions aren't used */
55// Returns an error message if there's a problem, NULL otherwise
56const char *validate_filters(const SDL_DialogFileFilter *filters,
57 int nfilters);
58
59const char *validate_list(const char *list);
diff --git a/contrib/SDL-3.2.8/src/dialog/android/SDL_androiddialog.c b/contrib/SDL-3.2.8/src/dialog/android/SDL_androiddialog.c
new file mode 100644
index 0000000..5eaf945
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/dialog/android/SDL_androiddialog.c
@@ -0,0 +1,58 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23#include "../SDL_dialog.h"
24#include "../../core/android/SDL_android.h"
25
26void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
27{
28 SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
29 int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0);
30 bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
31 bool is_save;
32
33 if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) {
34 SDL_SetError("File dialog driver unsupported (don't set SDL_HINT_FILE_DIALOG_DRIVER)");
35 callback(userdata, NULL, -1);
36 return;
37 }
38
39 switch (type) {
40 case SDL_FILEDIALOG_OPENFILE:
41 is_save = false;
42 break;
43
44 case SDL_FILEDIALOG_SAVEFILE:
45 is_save = true;
46 break;
47
48 case SDL_FILEDIALOG_OPENFOLDER:
49 SDL_Unsupported();
50 callback(userdata, NULL, -1);
51 return;
52 };
53
54 if (!Android_JNI_OpenFileDialog(callback, userdata, filters, nfilters, is_save, allow_many)) {
55 // SDL_SetError is already called when it fails
56 callback(userdata, NULL, -1);
57 }
58}
diff --git a/contrib/SDL-3.2.8/src/dialog/cocoa/SDL_cocoadialog.m b/contrib/SDL-3.2.8/src/dialog/cocoa/SDL_cocoadialog.m
new file mode 100644
index 0000000..fb9c5ad
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/dialog/cocoa/SDL_cocoadialog.m
@@ -0,0 +1,188 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22#include "../SDL_dialog.h"
23#include "../SDL_dialog_utils.h"
24
25#ifdef SDL_PLATFORM_MACOS
26
27#import <Cocoa/Cocoa.h>
28#import <UniformTypeIdentifiers/UTType.h>
29
30void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
31{
32 SDL_Window* window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL);
33 SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
34 int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0);
35 bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
36 const char* default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL);
37 const char* title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL);
38 const char* accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL);
39
40 if (filters) {
41 const char *msg = validate_filters(filters, nfilters);
42
43 if (msg) {
44 SDL_SetError("%s", msg);
45 callback(userdata, NULL, -1);
46 return;
47 }
48 }
49
50 if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) {
51 SDL_SetError("File dialog driver unsupported (don't set SDL_HINT_FILE_DIALOG_DRIVER)");
52 callback(userdata, NULL, -1);
53 return;
54 }
55
56 // NSOpenPanel inherits from NSSavePanel
57 NSSavePanel *dialog;
58 NSOpenPanel *dialog_as_open;
59
60 switch (type) {
61 case SDL_FILEDIALOG_SAVEFILE:
62 dialog = [NSSavePanel savePanel];
63 break;
64
65 case SDL_FILEDIALOG_OPENFILE:
66 dialog_as_open = [NSOpenPanel openPanel];
67 [dialog_as_open setAllowsMultipleSelection:((allow_many == true) ? YES : NO)];
68 dialog = dialog_as_open;
69 break;
70
71 case SDL_FILEDIALOG_OPENFOLDER:
72 dialog_as_open = [NSOpenPanel openPanel];
73 [dialog_as_open setCanChooseFiles:NO];
74 [dialog_as_open setCanChooseDirectories:YES];
75 [dialog_as_open setAllowsMultipleSelection:((allow_many == true) ? YES : NO)];
76 dialog = dialog_as_open;
77 break;
78 };
79
80 if (title) {
81 [dialog setTitle:[NSString stringWithUTF8String:title]];
82 }
83
84 if (accept) {
85 [dialog setPrompt:[NSString stringWithUTF8String:accept]];
86 }
87
88 if (filters) {
89 // On macOS 11.0 and up, this is an array of UTType. Prior to that, it's an array of NSString
90 NSMutableArray *types = [[NSMutableArray alloc] initWithCapacity:nfilters ];
91
92 int has_all_files = 0;
93 for (int i = 0; i < nfilters; i++) {
94 char *pattern = SDL_strdup(filters[i].pattern);
95 char *pattern_ptr = pattern;
96
97 if (!pattern_ptr) {
98 callback(userdata, NULL, -1);
99 return;
100 }
101
102 for (char *c = pattern; *c; c++) {
103 if (*c == ';') {
104 *c = '\0';
105 if(@available(macOS 11.0, *)) {
106 [types addObject: [UTType typeWithFilenameExtension:[NSString stringWithFormat: @"%s", pattern_ptr]]];
107 } else {
108 [types addObject: [NSString stringWithFormat: @"%s", pattern_ptr]];
109 }
110 pattern_ptr = c + 1;
111 } else if (*c == '*') {
112 has_all_files = 1;
113 }
114 }
115 if(@available(macOS 11.0, *)) {
116 [types addObject: [UTType typeWithFilenameExtension:[NSString stringWithFormat: @"%s", pattern_ptr]]];
117 } else {
118 [types addObject: [NSString stringWithFormat: @"%s", pattern_ptr]];
119 }
120
121 SDL_free(pattern);
122 }
123
124 if (!has_all_files) {
125 if (@available(macOS 11.0, *)) {
126 [dialog setAllowedContentTypes:types];
127 } else {
128 [dialog setAllowedFileTypes:types];
129 }
130 }
131 }
132
133 // Keep behavior consistent with other platforms
134 [dialog setAllowsOtherFileTypes:YES];
135
136 if (default_location) {
137 [dialog setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:default_location]]];
138 }
139
140 NSWindow *w = NULL;
141
142 if (window) {
143 w = (__bridge NSWindow *)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, NULL);
144 }
145
146 if (w) {
147 // [dialog beginWithCompletionHandler:^(NSInteger result) {
148 [dialog beginSheetModalForWindow:w completionHandler:^(NSInteger result) {
149 if (result == NSModalResponseOK) {
150 if (dialog_as_open) {
151 NSArray* urls = [dialog_as_open URLs];
152 const char *files[[urls count] + 1];
153 for (int i = 0; i < [urls count]; i++) {
154 files[i] = [[[urls objectAtIndex:i] path] UTF8String];
155 }
156 files[[urls count]] = NULL;
157 callback(userdata, files, -1);
158 } else {
159 const char *files[2] = { [[[dialog URL] path] UTF8String], NULL };
160 callback(userdata, files, -1);
161 }
162 } else if (result == NSModalResponseCancel) {
163 const char *files[1] = { NULL };
164 callback(userdata, files, -1);
165 }
166 }];
167 } else {
168 if ([dialog runModal] == NSModalResponseOK) {
169 if (dialog_as_open) {
170 NSArray* urls = [dialog_as_open URLs];
171 const char *files[[urls count] + 1];
172 for (int i = 0; i < [urls count]; i++) {
173 files[i] = [[[urls objectAtIndex:i] path] UTF8String];
174 }
175 files[[urls count]] = NULL;
176 callback(userdata, files, -1);
177 } else {
178 const char *files[2] = { [[[dialog URL] path] UTF8String], NULL };
179 callback(userdata, files, -1);
180 }
181 } else {
182 const char *files[1] = { NULL };
183 callback(userdata, files, -1);
184 }
185 }
186}
187
188#endif // SDL_PLATFORM_MACOS
diff --git a/contrib/SDL-3.2.8/src/dialog/dummy/SDL_dummydialog.c b/contrib/SDL-3.2.8/src/dialog/dummy/SDL_dummydialog.c
new file mode 100644
index 0000000..121a090
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/dialog/dummy/SDL_dummydialog.c
@@ -0,0 +1,33 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "../SDL_dialog.h"
24
25#ifdef SDL_DIALOG_DUMMY
26
27void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
28{
29 SDL_Unsupported();
30 callback(userdata, NULL, -1);
31}
32
33#endif // SDL_DIALOG_DUMMY
diff --git a/contrib/SDL-3.2.8/src/dialog/haiku/SDL_haikudialog.cc b/contrib/SDL-3.2.8/src/dialog/haiku/SDL_haikudialog.cc
new file mode 100644
index 0000000..d60e343
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/dialog/haiku/SDL_haikudialog.cc
@@ -0,0 +1,293 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22extern "C" {
23#include "../SDL_dialog.h"
24#include "../SDL_dialog_utils.h"
25}
26#include "../../core/haiku/SDL_BeApp.h"
27#include "../../video/haiku/SDL_BWin.h"
28
29#include <string>
30#include <vector>
31
32#include <sys/stat.h>
33
34#include <FilePanel.h>
35#include <Entry.h>
36#include <Looper.h>
37#include <Messenger.h>
38#include <Path.h>
39#include <TypeConstants.h>
40
41bool StringEndsWith(const std::string& str, const std::string& end)
42{
43 return str.size() >= end.size() && !str.compare(str.size() - end.size(), end.size(), end);
44}
45
46std::vector<std::string> StringSplit(const std::string& str, const std::string& split)
47{
48 std::vector<std::string> result;
49 std::string s = str;
50 size_t pos = 0;
51
52 while ((pos = s.find(split)) != std::string::npos) {
53 result.push_back(s.substr(0, pos));
54 s = s.substr(pos + split.size());
55 }
56
57 result.push_back(s);
58
59 return result;
60}
61
62class SDLBRefFilter : public BRefFilter
63{
64public:
65 SDLBRefFilter(const SDL_DialogFileFilter *filters, int nfilters) :
66 BRefFilter(),
67 m_filters(filters),
68 m_nfilters(nfilters)
69 {
70 }
71
72 virtual bool Filter(const entry_ref *ref, BNode *node, struct stat_beos *stat, const char *mimeType) override
73 {
74 BEntry entry(ref);
75 BPath path;
76 entry.GetPath(&path);
77 std::string result = path.Path();
78
79 if (!m_filters)
80 return true;
81
82 struct stat info;
83 node->GetStat(&info);
84 if (S_ISDIR(info.st_mode))
85 return true;
86
87 for (int i = 0; i < m_nfilters; i++) {
88 for (const auto& suffix : StringSplit(m_filters[i].pattern, ";")) {
89 if (StringEndsWith(result, std::string(".") + suffix)) {
90 return true;
91 }
92 }
93 }
94
95 return false;
96 }
97
98private:
99 const SDL_DialogFileFilter * const m_filters;
100 int m_nfilters;
101};
102
103class CallbackLooper : public BLooper
104{
105public:
106 CallbackLooper(SDL_DialogFileCallback callback, void *userdata) :
107 m_callback(callback),
108 m_userdata(userdata),
109 m_files(),
110 m_messenger(),
111 m_panel(),
112 m_filter()
113 {
114 }
115
116 ~CallbackLooper()
117 {
118 delete m_messenger;
119 delete m_panel;
120 delete m_filter;
121 }
122
123 void SetToBeFreed(BMessenger *messenger, BFilePanel *panel, SDLBRefFilter *filter)
124 {
125 m_messenger = messenger;
126 m_panel = panel;
127 m_filter = filter;
128 }
129
130 virtual void MessageReceived(BMessage *msg) override
131 {
132 entry_ref file;
133 BPath path;
134 BEntry entry;
135 std::string result;
136 const char *filename;
137 int32 nFiles = 0;
138
139 switch (msg->what)
140 {
141 case B_REFS_RECEIVED: // Open
142 msg->GetInfo("refs", NULL, &nFiles);
143 for (int i = 0; i < nFiles; i++) {
144 msg->FindRef("refs", i, &file);
145 entry.SetTo(&file);
146 entry.GetPath(&path);
147 result = path.Path();
148 m_files.push_back(result);
149 }
150 break;
151
152 case B_SAVE_REQUESTED: // Save
153 msg->FindRef("directory", &file);
154 entry.SetTo(&file);
155 entry.GetPath(&path);
156 result = path.Path();
157 result += "/";
158 msg->FindString("name", &filename);
159 result += filename;
160 m_files.push_back(result);
161 break;
162
163 case B_CANCEL: // Whenever the dialog is closed (Cancel but also after Open and Save)
164 {
165 nFiles = m_files.size();
166 const char* files[nFiles + 1];
167 for (int i = 0; i < nFiles; i++) {
168 files[i] = m_files[i].c_str();
169 }
170 files[nFiles] = NULL;
171 m_callback(m_userdata, files, -1);
172 Quit();
173 SDL_QuitBeApp();
174 delete this;
175 }
176 break;
177
178 default:
179 BHandler::MessageReceived(msg);
180 break;
181 }
182 }
183
184private:
185 SDL_DialogFileCallback m_callback;
186 void *m_userdata;
187 std::vector<std::string> m_files;
188
189 // Only to free stuff later
190 BMessenger *m_messenger;
191 BFilePanel *m_panel;
192 SDLBRefFilter *m_filter;
193};
194
195void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
196{
197 SDL_Window* window = (SDL_Window*) SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL);
198 SDL_DialogFileFilter* filters = (SDL_DialogFileFilter*) SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
199 int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0);
200 bool many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
201 const char* location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL);
202 const char* title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL);
203 const char* accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL);
204 const char* cancel = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_CANCEL_STRING, NULL);
205
206 bool modal = !!window;
207
208 bool save = false;
209 bool folder = false;
210
211 switch (type) {
212 case SDL_FILEDIALOG_SAVEFILE:
213 save = true;
214 break;
215
216 case SDL_FILEDIALOG_OPENFILE:
217 break;
218
219 case SDL_FILEDIALOG_OPENFOLDER:
220 folder = true;
221 break;
222 };
223
224 if (!SDL_InitBeApp()) {
225 char* err = SDL_strdup(SDL_GetError());
226 SDL_SetError("Couldn't init Be app: %s", err);
227 SDL_free(err);
228 callback(userdata, NULL, -1);
229 return;
230 }
231
232 if (filters) {
233 const char *msg = validate_filters(filters, nfilters);
234
235 if (msg) {
236 SDL_SetError("%s", msg);
237 callback(userdata, NULL, -1);
238 return;
239 }
240 }
241
242 if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) {
243 SDL_SetError("File dialog driver unsupported");
244 callback(userdata, NULL, -1);
245 return;
246 }
247
248 // No unique_ptr's because they need to survive the end of the function
249 CallbackLooper *looper = new(std::nothrow) CallbackLooper(callback, userdata);
250 BMessenger *messenger = new(std::nothrow) BMessenger(NULL, looper);
251 SDLBRefFilter *filter = new(std::nothrow) SDLBRefFilter(filters, nfilters);
252
253 if (looper == NULL || messenger == NULL || filter == NULL) {
254 SDL_free(looper);
255 SDL_free(messenger);
256 SDL_free(filter);
257 SDL_OutOfMemory();
258 callback(userdata, NULL, -1);
259 return;
260 }
261
262 BEntry entry;
263 entry_ref entryref;
264 if (location) {
265 entry.SetTo(location);
266 entry.GetRef(&entryref);
267 }
268
269 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);
270
271 if (title) {
272 panel->Window()->SetTitle(title);
273 }
274
275 if (accept) {
276 panel->SetButtonLabel(B_DEFAULT_BUTTON, accept);
277 }
278
279 if (cancel) {
280 panel->SetButtonLabel(B_CANCEL_BUTTON, cancel);
281 }
282
283 if (window) {
284 SDL_BWin *bwin = (SDL_BWin *)(window->internal);
285 panel->Window()->SetLook(B_MODAL_WINDOW_LOOK);
286 panel->Window()->SetFeel(B_MODAL_SUBSET_WINDOW_FEEL);
287 panel->Window()->AddToSubset(bwin);
288 }
289
290 looper->SetToBeFreed(messenger, panel, filter);
291 looper->Run();
292 panel->Show();
293}
diff --git a/contrib/SDL-3.2.8/src/dialog/unix/SDL_portaldialog.c b/contrib/SDL-3.2.8/src/dialog/unix/SDL_portaldialog.c
new file mode 100644
index 0000000..efecd12
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/dialog/unix/SDL_portaldialog.c
@@ -0,0 +1,545 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22#include "../SDL_dialog_utils.h"
23
24#include "../../core/linux/SDL_dbus.h"
25
26#ifdef SDL_USE_LIBDBUS
27
28#include <errno.h>
29#include <sys/types.h>
30#include <sys/wait.h>
31#include <unistd.h>
32
33#define PORTAL_DESTINATION "org.freedesktop.portal.Desktop"
34#define PORTAL_PATH "/org/freedesktop/portal/desktop"
35#define PORTAL_INTERFACE "org.freedesktop.portal.FileChooser"
36
37#define SIGNAL_SENDER "org.freedesktop.portal.Desktop"
38#define SIGNAL_INTERFACE "org.freedesktop.portal.Request"
39#define SIGNAL_NAME "Response"
40#define SIGNAL_FILTER "type='signal', sender='"SIGNAL_SENDER"', interface='"SIGNAL_INTERFACE"', member='"SIGNAL_NAME"', path='"
41
42#define HANDLE_LEN 10
43
44#define WAYLAND_HANDLE_PREFIX "wayland:"
45#define X11_HANDLE_PREFIX "x11:"
46
47typedef struct {
48 SDL_DialogFileCallback callback;
49 void *userdata;
50 const char *path;
51} SignalCallback;
52
53static void DBus_AppendStringOption(SDL_DBusContext *dbus, DBusMessageIter *options, const char *key, const char *value)
54{
55 DBusMessageIter options_pair, options_value;
56
57 dbus->message_iter_open_container(options, DBUS_TYPE_DICT_ENTRY, NULL, &options_pair);
58 dbus->message_iter_append_basic(&options_pair, DBUS_TYPE_STRING, &key);
59 dbus->message_iter_open_container(&options_pair, DBUS_TYPE_VARIANT, "s", &options_value);
60 dbus->message_iter_append_basic(&options_value, DBUS_TYPE_STRING, &value);
61 dbus->message_iter_close_container(&options_pair, &options_value);
62 dbus->message_iter_close_container(options, &options_pair);
63}
64
65static void DBus_AppendBoolOption(SDL_DBusContext *dbus, DBusMessageIter *options, const char *key, int value)
66{
67 DBusMessageIter options_pair, options_value;
68
69 dbus->message_iter_open_container(options, DBUS_TYPE_DICT_ENTRY, NULL, &options_pair);
70 dbus->message_iter_append_basic(&options_pair, DBUS_TYPE_STRING, &key);
71 dbus->message_iter_open_container(&options_pair, DBUS_TYPE_VARIANT, "b", &options_value);
72 dbus->message_iter_append_basic(&options_value, DBUS_TYPE_BOOLEAN, &value);
73 dbus->message_iter_close_container(&options_pair, &options_value);
74 dbus->message_iter_close_container(options, &options_pair);
75}
76
77static void DBus_AppendFilter(SDL_DBusContext *dbus, DBusMessageIter *parent, const SDL_DialogFileFilter filter)
78{
79 DBusMessageIter filter_entry, filter_array, filter_array_entry;
80 char *state = NULL, *patterns, *pattern, *glob_pattern;
81 int zero = 0;
82
83 dbus->message_iter_open_container(parent, DBUS_TYPE_STRUCT, NULL, &filter_entry);
84 dbus->message_iter_append_basic(&filter_entry, DBUS_TYPE_STRING, &filter.name);
85 dbus->message_iter_open_container(&filter_entry, DBUS_TYPE_ARRAY, "(us)", &filter_array);
86
87 patterns = SDL_strdup(filter.pattern);
88 if (!patterns) {
89 goto cleanup;
90 }
91
92 pattern = SDL_strtok_r(patterns, ";", &state);
93 while (pattern) {
94 size_t max_len = SDL_strlen(pattern) + 3;
95
96 dbus->message_iter_open_container(&filter_array, DBUS_TYPE_STRUCT, NULL, &filter_array_entry);
97 dbus->message_iter_append_basic(&filter_array_entry, DBUS_TYPE_UINT32, &zero);
98
99 glob_pattern = SDL_calloc(max_len, sizeof(char));
100 if (!glob_pattern) {
101 goto cleanup;
102 }
103 glob_pattern[0] = '*';
104 /* Special case: The '*' filter doesn't need to be rewritten */
105 if (pattern[0] != '*' || pattern[1]) {
106 glob_pattern[1] = '.';
107 SDL_strlcat(glob_pattern + 2, pattern, max_len);
108 }
109 dbus->message_iter_append_basic(&filter_array_entry, DBUS_TYPE_STRING, &glob_pattern);
110 SDL_free(glob_pattern);
111
112 dbus->message_iter_close_container(&filter_array, &filter_array_entry);
113 pattern = SDL_strtok_r(NULL, ";", &state);
114 }
115
116cleanup:
117 SDL_free(patterns);
118
119 dbus->message_iter_close_container(&filter_entry, &filter_array);
120 dbus->message_iter_close_container(parent, &filter_entry);
121}
122
123static void DBus_AppendFilters(SDL_DBusContext *dbus, DBusMessageIter *options, const SDL_DialogFileFilter *filters, int nfilters)
124{
125 DBusMessageIter options_pair, options_value, options_value_array;
126 static const char *filters_name = "filters";
127
128 dbus->message_iter_open_container(options, DBUS_TYPE_DICT_ENTRY, NULL, &options_pair);
129 dbus->message_iter_append_basic(&options_pair, DBUS_TYPE_STRING, &filters_name);
130 dbus->message_iter_open_container(&options_pair, DBUS_TYPE_VARIANT, "a(sa(us))", &options_value);
131 dbus->message_iter_open_container(&options_value, DBUS_TYPE_ARRAY, "(sa(us))", &options_value_array);
132 for (int i = 0; i < nfilters; i++) {
133 DBus_AppendFilter(dbus, &options_value_array, filters[i]);
134 }
135 dbus->message_iter_close_container(&options_value, &options_value_array);
136 dbus->message_iter_close_container(&options_pair, &options_value);
137 dbus->message_iter_close_container(options, &options_pair);
138}
139
140static void DBus_AppendByteArray(SDL_DBusContext *dbus, DBusMessageIter *options, const char *key, const char *value)
141{
142 DBusMessageIter options_pair, options_value, options_array;
143
144 dbus->message_iter_open_container(options, DBUS_TYPE_DICT_ENTRY, NULL, &options_pair);
145 dbus->message_iter_append_basic(&options_pair, DBUS_TYPE_STRING, &key);
146 dbus->message_iter_open_container(&options_pair, DBUS_TYPE_VARIANT, "ay", &options_value);
147 dbus->message_iter_open_container(&options_value, DBUS_TYPE_ARRAY, "y", &options_array);
148 do {
149 dbus->message_iter_append_basic(&options_array, DBUS_TYPE_BYTE, value);
150 } while (*value++);
151 dbus->message_iter_close_container(&options_value, &options_array);
152 dbus->message_iter_close_container(&options_pair, &options_value);
153 dbus->message_iter_close_container(options, &options_pair);
154}
155
156static DBusHandlerResult DBus_MessageFilter(DBusConnection *conn, DBusMessage *msg, void *data)
157{
158 SDL_DBusContext *dbus = SDL_DBus_GetContext();
159 SignalCallback *signal_data = (SignalCallback *)data;
160
161 if (dbus->message_is_signal(msg, SIGNAL_INTERFACE, SIGNAL_NAME) &&
162 dbus->message_has_path(msg, signal_data->path)) {
163 DBusMessageIter signal_iter, result_array, array_entry, value_entry, uri_entry;
164 uint32_t result;
165 size_t length = 2, current = 0;
166 const char **path = NULL;
167
168 dbus->message_iter_init(msg, &signal_iter);
169 // Check if the parameters are what we expect
170 if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_UINT32) {
171 goto not_our_signal;
172 }
173 dbus->message_iter_get_basic(&signal_iter, &result);
174
175 if (result == 1 || result == 2) {
176 // cancelled
177 const char *result_data[] = { NULL };
178 signal_data->callback(signal_data->userdata, result_data, -1); // TODO: Set this to the last selected filter
179 goto done;
180
181 } else if (result) {
182 // some error occurred
183 signal_data->callback(signal_data->userdata, NULL, -1);
184 goto done;
185 }
186
187 if (!dbus->message_iter_next(&signal_iter)) {
188 goto not_our_signal;
189 }
190
191 if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_ARRAY) {
192 goto not_our_signal;
193 }
194
195 dbus->message_iter_recurse(&signal_iter, &result_array);
196
197 while (dbus->message_iter_get_arg_type(&result_array) == DBUS_TYPE_DICT_ENTRY) {
198 const char *method;
199
200 dbus->message_iter_recurse(&result_array, &array_entry);
201 if (dbus->message_iter_get_arg_type(&array_entry) != DBUS_TYPE_STRING) {
202 goto not_our_signal;
203 }
204
205 dbus->message_iter_get_basic(&array_entry, &method);
206 if (!SDL_strcmp(method, "uris")) {
207 // we only care about the selected file paths
208 break;
209 }
210
211 if (!dbus->message_iter_next(&result_array)) {
212 goto not_our_signal;
213 }
214 }
215
216 if (!dbus->message_iter_next(&array_entry)) {
217 goto not_our_signal;
218 }
219
220 if (dbus->message_iter_get_arg_type(&array_entry) != DBUS_TYPE_VARIANT) {
221 goto not_our_signal;
222 }
223 dbus->message_iter_recurse(&array_entry, &value_entry);
224
225 if (dbus->message_iter_get_arg_type(&value_entry) != DBUS_TYPE_ARRAY) {
226 goto not_our_signal;
227 }
228 dbus->message_iter_recurse(&value_entry, &uri_entry);
229
230 path = SDL_malloc(length * sizeof(const char *));
231 if (!path) {
232 signal_data->callback(signal_data->userdata, NULL, -1);
233 goto done;
234 }
235
236 while (dbus->message_iter_get_arg_type(&uri_entry) == DBUS_TYPE_STRING) {
237 const char *uri = NULL;
238
239 if (current >= length - 1) {
240 ++length;
241 const char **newpath = SDL_realloc(path, length * sizeof(const char *));
242 if (!newpath) {
243 signal_data->callback(signal_data->userdata, NULL, -1);
244 goto done;
245 }
246 path = newpath;
247 }
248
249 dbus->message_iter_get_basic(&uri_entry, &uri);
250
251 // https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.FileChooser.html
252 // Returned paths will always start with 'file://'; SDL_URIToLocal() truncates it.
253 char *decoded_uri = SDL_malloc(SDL_strlen(uri) + 1);
254 if (SDL_URIToLocal(uri, decoded_uri)) {
255 path[current] = decoded_uri;
256 } else {
257 SDL_free(decoded_uri);
258 SDL_SetError("Portal dialogs: Unsupported protocol: %s", uri);
259 signal_data->callback(signal_data->userdata, NULL, -1);
260 goto done;
261 }
262
263 dbus->message_iter_next(&uri_entry);
264 ++current;
265 }
266 path[current] = NULL;
267 signal_data->callback(signal_data->userdata, path, -1); // TODO: Fetch the index of the filter that was used
268done:
269 dbus->connection_remove_filter(conn, &DBus_MessageFilter, signal_data);
270
271 if (path) {
272 for (size_t i = 0; i < current; ++i) {
273 SDL_free((char *)path[i]);
274 }
275 SDL_free(path);
276 }
277 SDL_free((void *)signal_data->path);
278 SDL_free(signal_data);
279 return DBUS_HANDLER_RESULT_HANDLED;
280 }
281
282not_our_signal:
283 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
284}
285
286void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
287{
288 const char *method;
289 const char *method_title;
290
291 SDL_Window* window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL);
292 SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
293 int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0);
294 bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
295 const char* default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL);
296 const char* accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL);
297 bool open_folders = false;
298
299 switch (type) {
300 case SDL_FILEDIALOG_OPENFILE:
301 method = "OpenFile";
302 method_title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, "Open File");
303 break;
304
305 case SDL_FILEDIALOG_SAVEFILE:
306 method = "SaveFile";
307 method_title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, "Save File");
308 break;
309
310 case SDL_FILEDIALOG_OPENFOLDER:
311 method = "OpenFile";
312 method_title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, "Open Folder");
313 open_folders = true;
314 break;
315
316 default:
317 /* This is already checked in ../SDL_dialog.c; this silences compiler warnings */
318 SDL_SetError("Invalid file dialog type: %d", type);
319 callback(userdata, NULL, -1);
320 return;
321 }
322
323 SDL_DBusContext *dbus = SDL_DBus_GetContext();
324 DBusMessage *msg;
325 DBusMessageIter params, options;
326 const char *signal_id = NULL;
327 char *handle_str, *filter;
328 int filter_len;
329 static uint32_t handle_id = 0;
330 static char *default_parent_window = "";
331 SDL_PropertiesID window_props = SDL_GetWindowProperties(window);
332
333 const char *err_msg = validate_filters(filters, nfilters);
334
335 if (err_msg) {
336 SDL_SetError("%s", err_msg);
337 callback(userdata, NULL, -1);
338 return;
339 }
340
341 if (dbus == NULL) {
342 SDL_SetError("Failed to connect to DBus");
343 callback(userdata, NULL, -1);
344 return;
345 }
346
347 msg = dbus->message_new_method_call(PORTAL_DESTINATION, PORTAL_PATH, PORTAL_INTERFACE, method);
348 if (msg == NULL) {
349 SDL_SetError("Failed to send message to portal");
350 callback(userdata, NULL, -1);
351 return;
352 }
353
354 dbus->message_iter_init_append(msg, &params);
355
356 handle_str = default_parent_window;
357 if (window_props) {
358 const char *parent_handle = SDL_GetStringProperty(window_props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_EXPORT_HANDLE_STRING, NULL);
359 if (parent_handle) {
360 size_t len = SDL_strlen(parent_handle);
361 len += sizeof(WAYLAND_HANDLE_PREFIX) + 1;
362 handle_str = SDL_malloc(len * sizeof(char));
363 if (!handle_str) {
364 callback(userdata, NULL, -1);
365 return;
366 }
367
368 SDL_snprintf(handle_str, len, "%s%s", WAYLAND_HANDLE_PREFIX, parent_handle);
369 } else {
370 const Uint64 xid = (Uint64)SDL_GetNumberProperty(window_props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
371 if (xid) {
372 const size_t len = sizeof(X11_HANDLE_PREFIX) + 24; // A 64-bit number can be 20 characters max.
373 handle_str = SDL_malloc(len * sizeof(char));
374 if (!handle_str) {
375 callback(userdata, NULL, -1);
376 return;
377 }
378
379 // The portal wants X11 window ID numbers in hex.
380 SDL_snprintf(handle_str, len, "%s%" SDL_PRIx64, X11_HANDLE_PREFIX, xid);
381 }
382 }
383 }
384
385 dbus->message_iter_append_basic(&params, DBUS_TYPE_STRING, &handle_str);
386 if (handle_str != default_parent_window) {
387 SDL_free(handle_str);
388 }
389
390 dbus->message_iter_append_basic(&params, DBUS_TYPE_STRING, &method_title);
391 dbus->message_iter_open_container(&params, DBUS_TYPE_ARRAY, "{sv}", &options);
392
393 handle_str = SDL_malloc(sizeof(char) * (HANDLE_LEN + 1));
394 if (!handle_str) {
395 callback(userdata, NULL, -1);
396 return;
397 }
398 SDL_snprintf(handle_str, HANDLE_LEN, "%u", ++handle_id);
399 DBus_AppendStringOption(dbus, &options, "handle_token", handle_str);
400 SDL_free(handle_str);
401
402 DBus_AppendBoolOption(dbus, &options, "modal", !!window);
403 if (allow_many) {
404 DBus_AppendBoolOption(dbus, &options, "multiple", 1);
405 }
406 if (open_folders) {
407 DBus_AppendBoolOption(dbus, &options, "directory", 1);
408 }
409 if (filters) {
410 DBus_AppendFilters(dbus, &options, filters, nfilters);
411 }
412 if (default_location) {
413 DBus_AppendByteArray(dbus, &options, "current_folder", default_location);
414 }
415 if (accept) {
416 DBus_AppendStringOption(dbus, &options, "accept_label", accept);
417 }
418 dbus->message_iter_close_container(&params, &options);
419
420 DBusMessage *reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_INFINITE, NULL);
421 if (reply) {
422 DBusMessageIter reply_iter;
423 dbus->message_iter_init(reply, &reply_iter);
424
425 if (dbus->message_iter_get_arg_type(&reply_iter) == DBUS_TYPE_OBJECT_PATH) {
426 dbus->message_iter_get_basic(&reply_iter, &signal_id);
427 }
428 }
429
430 if (!signal_id) {
431 SDL_SetError("Invalid response received by DBus");
432 callback(userdata, NULL, -1);
433 goto incorrect_type;
434 }
435
436 dbus->message_unref(msg);
437
438 filter_len = SDL_strlen(SIGNAL_FILTER) + SDL_strlen(signal_id) + 2;
439 filter = SDL_malloc(sizeof(char) * filter_len);
440 if (!filter) {
441 callback(userdata, NULL, -1);
442 goto incorrect_type;
443 }
444
445 SDL_snprintf(filter, filter_len, SIGNAL_FILTER"%s'", signal_id);
446 dbus->bus_add_match(dbus->session_conn, filter, NULL);
447 SDL_free(filter);
448
449 SignalCallback *data = SDL_malloc(sizeof(SignalCallback));
450 if (!data) {
451 callback(userdata, NULL, -1);
452 goto incorrect_type;
453 }
454 data->callback = callback;
455 data->userdata = userdata;
456 data->path = SDL_strdup(signal_id);
457 if (!data->path) {
458 SDL_free(data);
459 callback(userdata, NULL, -1);
460 goto incorrect_type;
461 }
462
463 /* TODO: This should be registered before opening the portal, or the filter will not catch
464 the message if it is sent before we register the filter.
465 */
466 dbus->connection_add_filter(dbus->session_conn,
467 &DBus_MessageFilter, data, NULL);
468 dbus->connection_flush(dbus->session_conn);
469
470incorrect_type:
471 dbus->message_unref(reply);
472}
473
474bool SDL_Portal_detect(void)
475{
476 SDL_DBusContext *dbus = SDL_DBus_GetContext();
477 DBusMessage *msg = NULL, *reply = NULL;
478 char *reply_str = NULL;
479 DBusMessageIter reply_iter;
480 static int portal_present = -1;
481
482 // No need for this if the result is cached.
483 if (portal_present != -1) {
484 return (portal_present > 0);
485 }
486
487 portal_present = 0;
488
489 if (!dbus) {
490 SDL_SetError("%s", "Failed to connect to DBus!");
491 return false;
492 }
493
494 // Use introspection to get the available services.
495 msg = dbus->message_new_method_call(PORTAL_DESTINATION, PORTAL_PATH, "org.freedesktop.DBus.Introspectable", "Introspect");
496 if (!msg) {
497 goto done;
498 }
499
500 reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, NULL);
501 dbus->message_unref(msg);
502 if (!reply) {
503 goto done;
504 }
505
506 if (!dbus->message_iter_init(reply, &reply_iter)) {
507 goto done;
508 }
509
510 if (dbus->message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_STRING) {
511 goto done;
512 }
513
514 /* Introspection gives us a dump of all the services on the destination in XML format, so search the
515 * giant string for the file chooser protocol.
516 */
517 dbus->message_iter_get_basic(&reply_iter, &reply_str);
518 if (SDL_strstr(reply_str, PORTAL_INTERFACE)) {
519 portal_present = 1; // Found it!
520 }
521
522done:
523 if (reply) {
524 dbus->message_unref(reply);
525 }
526
527 return (portal_present > 0);
528}
529
530#else
531
532// Dummy implementation to avoid compilation problems
533
534void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
535{
536 SDL_Unsupported();
537 callback(userdata, NULL, -1);
538}
539
540bool SDL_Portal_detect(void)
541{
542 return false;
543}
544
545#endif // SDL_USE_LIBDBUS
diff --git a/contrib/SDL-3.2.8/src/dialog/unix/SDL_portaldialog.h b/contrib/SDL-3.2.8/src/dialog/unix/SDL_portaldialog.h
new file mode 100644
index 0000000..4497287
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/dialog/unix/SDL_portaldialog.h
@@ -0,0 +1,27 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props);
25
26/** @returns non-zero if available, zero if unavailable */
27bool SDL_Portal_detect(void);
diff --git a/contrib/SDL-3.2.8/src/dialog/unix/SDL_unixdialog.c b/contrib/SDL-3.2.8/src/dialog/unix/SDL_unixdialog.c
new file mode 100644
index 0000000..bec2ac9
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/dialog/unix/SDL_unixdialog.c
@@ -0,0 +1,81 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "../SDL_dialog.h"
24#include "./SDL_portaldialog.h"
25#include "./SDL_zenitydialog.h"
26
27static void (*detected_function)(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) = NULL;
28
29void SDLCALL hint_callback(void *userdata, const char *name, const char *oldValue, const char *newValue);
30
31static void set_callback(void)
32{
33 static bool is_set = false;
34
35 if (is_set == false) {
36 is_set = true;
37 SDL_AddHintCallback(SDL_HINT_FILE_DIALOG_DRIVER, hint_callback, NULL);
38 }
39}
40
41// Returns non-zero on success, 0 on failure
42static int detect_available_methods(const char *value)
43{
44 const char *driver = value ? value : SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER);
45
46 set_callback();
47
48 if (driver == NULL || SDL_strcmp(driver, "portal") == 0) {
49 if (SDL_Portal_detect()) {
50 detected_function = SDL_Portal_ShowFileDialogWithProperties;
51 return 1;
52 }
53 }
54
55 if (driver == NULL || SDL_strcmp(driver, "zenity") == 0) {
56 if (SDL_Zenity_detect()) {
57 detected_function = SDL_Zenity_ShowFileDialogWithProperties;
58 return 2;
59 }
60 }
61
62 SDL_SetError("File dialog driver unsupported (supported values for SDL_HINT_FILE_DIALOG_DRIVER are 'zenity' and 'portal')");
63 return 0;
64}
65
66void SDLCALL hint_callback(void *userdata, const char *name, const char *oldValue, const char *newValue)
67{
68 detect_available_methods(newValue);
69}
70
71void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
72{
73 // Call detect_available_methods() again each time in case the situation changed
74 if (!detected_function && !detect_available_methods(NULL)) {
75 // SetError() done by detect_available_methods()
76 callback(userdata, NULL, -1);
77 return;
78 }
79
80 detected_function(type, callback, userdata, props);
81}
diff --git a/contrib/SDL-3.2.8/src/dialog/unix/SDL_zenitydialog.c b/contrib/SDL-3.2.8/src/dialog/unix/SDL_zenitydialog.c
new file mode 100644
index 0000000..4632c8e
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/dialog/unix/SDL_zenitydialog.c
@@ -0,0 +1,366 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "../SDL_dialog_utils.h"
24
25#define X11_HANDLE_MAX_WIDTH 28
26typedef struct
27{
28 SDL_DialogFileCallback callback;
29 void *userdata;
30 void *argv;
31
32 /* Zenity only works with X11 handles apparently */
33 char x11_window_handle[X11_HANDLE_MAX_WIDTH];
34 /* These are part of argv, but are tracked separately for deallocation purposes */
35 int nfilters;
36 char **filters_slice;
37 char *filename;
38 char *title;
39 char *accept;
40 char *cancel;
41} zenityArgs;
42
43static char *zenity_clean_name(const char *name)
44{
45 char *newname = SDL_strdup(name);
46
47 /* Filter out "|", which Zenity considers a special character. Let's hope
48 there aren't others. TODO: find something better. */
49 for (char *c = newname; *c; c++) {
50 if (*c == '|') {
51 // Zenity doesn't support escaping with '\'
52 *c = '/';
53 }
54 }
55
56 return newname;
57}
58
59static bool get_x11_window_handle(SDL_PropertiesID props, char *out)
60{
61 SDL_Window *window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL);
62 if (!window) {
63 return false;
64 }
65 SDL_PropertiesID window_props = SDL_GetWindowProperties(window);
66 if (!window_props) {
67 return false;
68 }
69 Uint64 handle = (Uint64)SDL_GetNumberProperty(window_props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
70 if (!handle) {
71 return false;
72 }
73 if (SDL_snprintf(out, X11_HANDLE_MAX_WIDTH, "0x%" SDL_PRIx64, handle) >= X11_HANDLE_MAX_WIDTH) {
74 return false;
75 };
76 return true;
77}
78
79/* Exec call format:
80 *
81 * zenity --file-selection --separator=\n [--multiple]
82 * [--directory] [--save --confirm-overwrite]
83 * [--filename FILENAME] [--modal --attach 0x11w1nd0w]
84 * [--title TITLE] [--ok-label ACCEPT]
85 * [--cancel-label CANCEL]
86 * [--file-filter=Filter Name | *.filt *.fn ...]...
87 */
88static zenityArgs *create_zenity_args(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
89{
90 zenityArgs *args = SDL_calloc(1, sizeof(*args));
91 if (!args) {
92 return NULL;
93 }
94 args->callback = callback;
95 args->userdata = userdata;
96 args->nfilters = SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0);
97
98 const char **argv = SDL_malloc(
99 sizeof(*argv) * (3 /* zenity --file-selection --separator=\n */
100 + 1 /* --multiple */
101 + 2 /* --directory | --save --confirm-overwrite */
102 + 2 /* --filename [file] */
103 + 3 /* --modal --attach [handle] */
104 + 2 /* --title [title] */
105 + 2 /* --ok-label [label] */
106 + 2 /* --cancel-label [label] */
107 + args->nfilters + 1 /* NULL */));
108 if (!argv) {
109 goto cleanup;
110 }
111 args->argv = argv;
112
113 /* Properties can be destroyed as soon as the function returns; copy over what we need. */
114#define COPY_STRING_PROPERTY(dst, prop) \
115 { \
116 const char *str = SDL_GetStringProperty(props, prop, NULL); \
117 if (str) { \
118 dst = SDL_strdup(str); \
119 if (!dst) { \
120 goto cleanup; \
121 } \
122 } \
123 }
124
125 COPY_STRING_PROPERTY(args->filename, SDL_PROP_FILE_DIALOG_LOCATION_STRING);
126 COPY_STRING_PROPERTY(args->title, SDL_PROP_FILE_DIALOG_TITLE_STRING);
127 COPY_STRING_PROPERTY(args->accept, SDL_PROP_FILE_DIALOG_ACCEPT_STRING);
128 COPY_STRING_PROPERTY(args->cancel, SDL_PROP_FILE_DIALOG_CANCEL_STRING);
129#undef COPY_STRING_PROPERTY
130
131 // ARGV PASS
132 int argc = 0;
133 argv[argc++] = "zenity";
134 argv[argc++] = "--file-selection";
135 argv[argc++] = "--separator=\n";
136
137 if (SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false)) {
138 argv[argc++] = "--multiple";
139 }
140
141 switch (type) {
142 case SDL_FILEDIALOG_OPENFILE:
143 break;
144
145 case SDL_FILEDIALOG_SAVEFILE:
146 argv[argc++] = "--save";
147 /* Asking before overwriting while saving seems like a sane default */
148 argv[argc++] = "--confirm-overwrite";
149 break;
150
151 case SDL_FILEDIALOG_OPENFOLDER:
152 argv[argc++] = "--directory";
153 break;
154 };
155
156 if (args->filename) {
157 argv[argc++] = "--filename";
158 argv[argc++] = args->filename;
159 }
160
161 if (get_x11_window_handle(props, args->x11_window_handle)) {
162 argv[argc++] = "--modal";
163 argv[argc++] = "--attach";
164 argv[argc++] = args->x11_window_handle;
165 }
166
167 if (args->title) {
168 argv[argc++] = "--title";
169 argv[argc++] = args->title;
170 }
171
172 if (args->accept) {
173 argv[argc++] = "--ok-label";
174 argv[argc++] = args->accept;
175 }
176
177 if (args->cancel) {
178 argv[argc++] = "--cancel-label";
179 argv[argc++] = args->cancel;
180 }
181
182 const SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
183 if (filters) {
184 args->filters_slice = (char **)&argv[argc];
185 for (int i = 0; i < args->nfilters; i++) {
186 char *filter_str = convert_filter(filters[i],
187 zenity_clean_name,
188 "--file-filter=", " | ", "",
189 "*.", " *.", "");
190
191 if (!filter_str) {
192 while (i--) {
193 SDL_free(args->filters_slice[i]);
194 }
195 goto cleanup;
196 }
197
198 args->filters_slice[i] = filter_str;
199 }
200 argc += args->nfilters;
201 }
202
203 argv[argc] = NULL;
204 return args;
205
206cleanup:
207 SDL_free(args->filename);
208 SDL_free(args->title);
209 SDL_free(args->accept);
210 SDL_free(args->cancel);
211 SDL_free(argv);
212 SDL_free(args);
213 return NULL;
214}
215
216// TODO: Zenity survives termination of the parent
217
218static void run_zenity(SDL_DialogFileCallback callback, void *userdata, void *argv)
219{
220 SDL_Process *process = NULL;
221 SDL_Environment *env = NULL;
222 int status = -1;
223 size_t bytes_read = 0;
224 char *container = NULL;
225 size_t narray = 1;
226 char **array = NULL;
227 bool result = false;
228
229 env = SDL_CreateEnvironment(true);
230 if (!env) {
231 goto done;
232 }
233
234 /* Recent versions of Zenity have different exit codes, but picks up
235 different codes from the environment */
236 SDL_SetEnvironmentVariable(env, "ZENITY_OK", "0", true);
237 SDL_SetEnvironmentVariable(env, "ZENITY_CANCEL", "1", true);
238 SDL_SetEnvironmentVariable(env, "ZENITY_ESC", "1", true);
239 SDL_SetEnvironmentVariable(env, "ZENITY_EXTRA", "2", true);
240 SDL_SetEnvironmentVariable(env, "ZENITY_ERROR", "2", true);
241 SDL_SetEnvironmentVariable(env, "ZENITY_TIMEOUT", "2", true);
242
243 SDL_PropertiesID props = SDL_CreateProperties();
244 SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, argv);
245 SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, env);
246 SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_NULL);
247 SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP);
248 SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_NULL);
249 process = SDL_CreateProcessWithProperties(props);
250 SDL_DestroyProperties(props);
251 if (!process) {
252 goto done;
253 }
254
255 container = SDL_ReadProcess(process, &bytes_read, &status);
256 if (!container) {
257 goto done;
258 }
259
260 array = (char **)SDL_malloc((narray + 1) * sizeof(char *));
261 if (!array) {
262 goto done;
263 }
264 array[0] = container;
265 array[1] = NULL;
266
267 for (int i = 0; i < bytes_read; i++) {
268 if (container[i] == '\n') {
269 container[i] = '\0';
270 // Reading from a process often leaves a trailing \n, so ignore the last one
271 if (i < bytes_read - 1) {
272 array[narray] = container + i + 1;
273 narray++;
274 char **new_array = (char **)SDL_realloc(array, (narray + 1) * sizeof(char *));
275 if (!new_array) {
276 goto done;
277 }
278 array = new_array;
279 array[narray] = NULL;
280 }
281 }
282 }
283
284 // 0 = the user chose one or more files, 1 = the user canceled the dialog
285 if (status == 0 || status == 1) {
286 callback(userdata, (const char *const *)array, -1);
287 } else {
288 SDL_SetError("Could not run zenity: exit code %d", status);
289 callback(userdata, NULL, -1);
290 }
291
292 result = true;
293
294done:
295 SDL_free(array);
296 SDL_free(container);
297 SDL_DestroyEnvironment(env);
298 SDL_DestroyProcess(process);
299
300 if (!result) {
301 callback(userdata, NULL, -1);
302 }
303}
304
305static void free_zenity_args(zenityArgs *args)
306{
307 if (args->filters_slice) {
308 for (int i = 0; i < args->nfilters; i++) {
309 SDL_free(args->filters_slice[i]);
310 }
311 }
312 SDL_free(args->filename);
313 SDL_free(args->title);
314 SDL_free(args->accept);
315 SDL_free(args->cancel);
316 SDL_free(args->argv);
317 SDL_free(args);
318}
319
320static int run_zenity_thread(void *ptr)
321{
322 zenityArgs *args = ptr;
323 run_zenity(args->callback, args->userdata, args->argv);
324 free_zenity_args(args);
325 return 0;
326}
327
328void SDL_Zenity_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
329{
330 zenityArgs *args = create_zenity_args(type, callback, userdata, props);
331 if (!args) {
332 callback(userdata, NULL, -1);
333 return;
334 }
335
336 SDL_Thread *thread = SDL_CreateThread(run_zenity_thread, "SDL_ZenityFileDialog", (void *)args);
337
338 if (!thread) {
339 free_zenity_args(args);
340 callback(userdata, NULL, -1);
341 return;
342 }
343
344 SDL_DetachThread(thread);
345}
346
347bool SDL_Zenity_detect(void)
348{
349 const char *args[] = {
350 "zenity", "--version", NULL
351 };
352 int status = -1;
353
354 SDL_PropertiesID props = SDL_CreateProperties();
355 SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, args);
356 SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_NULL);
357 SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_NULL);
358 SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_NULL);
359 SDL_Process *process = SDL_CreateProcessWithProperties(props);
360 SDL_DestroyProperties(props);
361 if (process) {
362 SDL_WaitProcess(process, true, &status);
363 SDL_DestroyProcess(process);
364 }
365 return (status == 0);
366}
diff --git a/contrib/SDL-3.2.8/src/dialog/unix/SDL_zenitydialog.h b/contrib/SDL-3.2.8/src/dialog/unix/SDL_zenitydialog.h
new file mode 100644
index 0000000..4cfe892
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/dialog/unix/SDL_zenitydialog.h
@@ -0,0 +1,27 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24extern void SDL_Zenity_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props);
25
26/** @returns non-zero if available, zero if unavailable */
27extern bool SDL_Zenity_detect(void);
diff --git a/contrib/SDL-3.2.8/src/dialog/windows/SDL_windowsdialog.c b/contrib/SDL-3.2.8/src/dialog/windows/SDL_windowsdialog.c
new file mode 100644
index 0000000..2de224f
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/dialog/windows/SDL_windowsdialog.c
@@ -0,0 +1,611 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22#include "../SDL_dialog.h"
23#include "../SDL_dialog_utils.h"
24
25#include <windows.h>
26#include <commdlg.h>
27#include <shlobj.h>
28#include "../../core/windows/SDL_windows.h"
29#include "../../thread/SDL_systhread.h"
30
31// If this number is too small, selecting too many files will give an error
32#define SELECTLIST_SIZE 65536
33
34typedef struct
35{
36 bool is_save;
37 wchar_t *filters_str;
38 char *default_file;
39 SDL_Window *parent;
40 DWORD flags;
41 SDL_DialogFileCallback callback;
42 void *userdata;
43 char *title;
44 char *accept;
45 char *cancel;
46} winArgs;
47
48typedef struct
49{
50 SDL_Window *parent;
51 SDL_DialogFileCallback callback;
52 char *default_folder;
53 void *userdata;
54 char *title;
55 char *accept;
56 char *cancel;
57} winFArgs;
58
59void freeWinArgs(winArgs *args)
60{
61 SDL_free(args->default_file);
62 SDL_free(args->filters_str);
63 SDL_free(args->title);
64 SDL_free(args->accept);
65 SDL_free(args->cancel);
66
67 SDL_free(args);
68}
69
70void freeWinFArgs(winFArgs *args)
71{
72 SDL_free(args->default_folder);
73 SDL_free(args->title);
74 SDL_free(args->accept);
75 SDL_free(args->cancel);
76
77 SDL_free(args);
78}
79
80/** Converts dialog.nFilterIndex to SDL-compatible value */
81int getFilterIndex(int as_reported_by_windows)
82{
83 return as_reported_by_windows - 1;
84}
85
86char *clear_filt_names(const char *filt)
87{
88 char *cleared = SDL_strdup(filt);
89
90 for (char *c = cleared; *c; c++) {
91 /* 0x01 bytes are used as temporary replacement for the various 0x00
92 bytes required by Win32 (one null byte between each filter, two at
93 the end of the filters). Filter out these bytes from the filter names
94 to avoid early-ending the filters if someone puts two consecutive
95 0x01 bytes in their filter names. */
96 if (*c == '\x01') {
97 *c = ' ';
98 }
99 }
100
101 return cleared;
102}
103
104// TODO: The new version of file dialogs
105void windows_ShowFileDialog(void *ptr)
106{
107 winArgs *args = (winArgs *) ptr;
108 bool is_save = args->is_save;
109 const char *default_file = args->default_file;
110 SDL_Window *parent = args->parent;
111 DWORD flags = args->flags;
112 SDL_DialogFileCallback callback = args->callback;
113 void *userdata = args->userdata;
114 const char *title = args->title;
115 wchar_t *filter_wchar = args->filters_str;
116
117 /* GetOpenFileName and GetSaveFileName have the same signature
118 (yes, LPOPENFILENAMEW even for the save dialog) */
119 typedef BOOL (WINAPI *pfnGetAnyFileNameW)(LPOPENFILENAMEW);
120 typedef DWORD (WINAPI *pfnCommDlgExtendedError)(void);
121 HMODULE lib = LoadLibraryW(L"Comdlg32.dll");
122 pfnGetAnyFileNameW pGetAnyFileName = NULL;
123 pfnCommDlgExtendedError pCommDlgExtendedError = NULL;
124
125 if (lib) {
126 pGetAnyFileName = (pfnGetAnyFileNameW) GetProcAddress(lib, is_save ? "GetSaveFileNameW" : "GetOpenFileNameW");
127 pCommDlgExtendedError = (pfnCommDlgExtendedError) GetProcAddress(lib, "CommDlgExtendedError");
128 } else {
129 SDL_SetError("Couldn't load Comdlg32.dll");
130 callback(userdata, NULL, -1);
131 return;
132 }
133
134 if (!pGetAnyFileName) {
135 SDL_SetError("Couldn't load GetOpenFileName/GetSaveFileName from library");
136 callback(userdata, NULL, -1);
137 return;
138 }
139
140 if (!pCommDlgExtendedError) {
141 SDL_SetError("Couldn't load CommDlgExtendedError from library");
142 callback(userdata, NULL, -1);
143 return;
144 }
145
146 HWND window = NULL;
147
148 if (parent) {
149 window = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(parent), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
150 }
151
152 wchar_t *filebuffer; // lpstrFile
153 wchar_t initfolder[MAX_PATH] = L""; // lpstrInitialDir
154
155 /* If SELECTLIST_SIZE is too large, putting filebuffer on the stack might
156 cause an overflow */
157 filebuffer = (wchar_t *) SDL_malloc(SELECTLIST_SIZE * sizeof(wchar_t));
158
159 // Necessary for the return code below
160 SDL_memset(filebuffer, 0, SELECTLIST_SIZE * sizeof(wchar_t));
161
162 if (default_file) {
163 /* On Windows 10, 11 and possibly others, lpstrFile can be initialized
164 with a path and the dialog will start at that location, but *only if
165 the path contains a filename*. If it ends with a folder (directory
166 separator), it fails with 0x3002 (12290) FNERR_INVALIDFILENAME. For
167 that specific case, lpstrInitialDir must be used instead, but just
168 for that case, because lpstrInitialDir doesn't support file names.
169
170 On top of that, lpstrInitialDir hides a special algorithm that
171 decides which folder to actually use as starting point, which may or
172 may not be the one provided, or some other unrelated folder. Also,
173 the algorithm changes between platforms. Assuming the documentation
174 is correct, the algorithm is there under 'lpstrInitialDir':
175
176 https://learn.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-openfilenamew
177
178 Finally, lpstrFile does not support forward slashes. lpstrInitialDir
179 does, though. */
180
181 char last_c = default_file[SDL_strlen(default_file) - 1];
182
183 if (last_c == '\\' || last_c == '/') {
184 MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, default_file, -1, initfolder, MAX_PATH);
185 } else {
186 MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, default_file, -1, filebuffer, MAX_PATH);
187
188 for (int i = 0; i < SELECTLIST_SIZE; i++) {
189 if (filebuffer[i] == L'/') {
190 filebuffer[i] = L'\\';
191 }
192 }
193 }
194 }
195
196 wchar_t *title_w = NULL;
197
198 if (title) {
199 title_w = WIN_UTF8ToStringW(title);
200 if (!title_w) {
201 SDL_free(filebuffer);
202 callback(userdata, NULL, -1);
203 return;
204 }
205 }
206
207 OPENFILENAMEW dialog;
208 dialog.lStructSize = sizeof(OPENFILENAME);
209 dialog.hwndOwner = window;
210 dialog.hInstance = 0;
211 dialog.lpstrFilter = filter_wchar;
212 dialog.lpstrCustomFilter = NULL;
213 dialog.nMaxCustFilter = 0;
214 dialog.nFilterIndex = 0;
215 dialog.lpstrFile = filebuffer;
216 dialog.nMaxFile = SELECTLIST_SIZE;
217 dialog.lpstrFileTitle = NULL;
218 dialog.lpstrInitialDir = *initfolder ? initfolder : NULL;
219 dialog.lpstrTitle = title_w;
220 dialog.Flags = flags | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_NOCHANGEDIR;
221 dialog.nFileOffset = 0;
222 dialog.nFileExtension = 0;
223 dialog.lpstrDefExt = NULL;
224 dialog.lCustData = 0;
225 dialog.lpfnHook = NULL;
226 dialog.lpTemplateName = NULL;
227 // Skipped many mac-exclusive and reserved members
228 dialog.FlagsEx = 0;
229
230 BOOL result = pGetAnyFileName(&dialog);
231
232 SDL_free(title_w);
233
234 if (result) {
235 if (!(flags & OFN_ALLOWMULTISELECT)) {
236 // File is a C string stored in dialog.lpstrFile
237 char *chosen_file = WIN_StringToUTF8W(dialog.lpstrFile);
238 const char *opts[2] = { chosen_file, NULL };
239 callback(userdata, opts, getFilterIndex(dialog.nFilterIndex));
240 SDL_free(chosen_file);
241 } else {
242 /* File is either a C string if the user chose a single file, else
243 it's a series of strings formatted like:
244
245 "C:\\path\\to\\folder\0filename1.ext\0filename2.ext\0\0"
246
247 The code below will only stop on a double NULL in all cases, so
248 it is important that the rest of the buffer has been zeroed. */
249 char chosen_folder[MAX_PATH];
250 char chosen_file[MAX_PATH];
251 wchar_t *file_ptr = dialog.lpstrFile;
252 size_t nfiles = 0;
253 size_t chosen_folder_size;
254 char **chosen_files_list = (char **) SDL_malloc(sizeof(char *) * (nfiles + 1));
255
256 if (!chosen_files_list) {
257 callback(userdata, NULL, -1);
258 SDL_free(filebuffer);
259 return;
260 }
261
262 chosen_files_list[nfiles] = NULL;
263
264 if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_folder, MAX_PATH, NULL, NULL) >= MAX_PATH) {
265 SDL_SetError("Path too long or invalid character in path");
266 SDL_free(chosen_files_list);
267 callback(userdata, NULL, -1);
268 SDL_free(filebuffer);
269 return;
270 }
271
272 chosen_folder_size = SDL_strlen(chosen_folder);
273 SDL_strlcpy(chosen_file, chosen_folder, MAX_PATH);
274 chosen_file[chosen_folder_size] = '\\';
275
276 file_ptr += SDL_strlen(chosen_folder) + 1;
277
278 while (*file_ptr) {
279 nfiles++;
280 char **new_cfl = (char **) SDL_realloc(chosen_files_list, sizeof(char*) * (nfiles + 1));
281
282 if (!new_cfl) {
283 for (size_t i = 0; i < nfiles - 1; i++) {
284 SDL_free(chosen_files_list[i]);
285 }
286
287 SDL_free(chosen_files_list);
288 callback(userdata, NULL, -1);
289 SDL_free(filebuffer);
290 return;
291 }
292
293 chosen_files_list = new_cfl;
294 chosen_files_list[nfiles] = NULL;
295
296 int diff = ((int) chosen_folder_size) + 1;
297
298 if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_file + diff, MAX_PATH - diff, NULL, NULL) >= MAX_PATH - diff) {
299 SDL_SetError("Path too long or invalid character in path");
300
301 for (size_t i = 0; i < nfiles - 1; i++) {
302 SDL_free(chosen_files_list[i]);
303 }
304
305 SDL_free(chosen_files_list);
306 callback(userdata, NULL, -1);
307 SDL_free(filebuffer);
308 return;
309 }
310
311 file_ptr += SDL_strlen(chosen_file) + 1 - diff;
312
313 chosen_files_list[nfiles - 1] = SDL_strdup(chosen_file);
314
315 if (!chosen_files_list[nfiles - 1]) {
316 for (size_t i = 0; i < nfiles - 1; i++) {
317 SDL_free(chosen_files_list[i]);
318 }
319
320 SDL_free(chosen_files_list);
321 callback(userdata, NULL, -1);
322 SDL_free(filebuffer);
323 return;
324 }
325 }
326
327 // If the user chose only one file, it's all just one string
328 if (nfiles == 0) {
329 nfiles++;
330 char **new_cfl = (char **) SDL_realloc(chosen_files_list, sizeof(char*) * (nfiles + 1));
331
332 if (!new_cfl) {
333 SDL_free(chosen_files_list);
334 callback(userdata, NULL, -1);
335 SDL_free(filebuffer);
336 return;
337 }
338
339 chosen_files_list = new_cfl;
340 chosen_files_list[nfiles] = NULL;
341 chosen_files_list[nfiles - 1] = SDL_strdup(chosen_folder);
342
343 if (!chosen_files_list[nfiles - 1]) {
344 SDL_free(chosen_files_list);
345 callback(userdata, NULL, -1);
346 SDL_free(filebuffer);
347 return;
348 }
349 }
350
351 callback(userdata, (const char * const*) chosen_files_list, getFilterIndex(dialog.nFilterIndex));
352
353 for (size_t i = 0; i < nfiles; i++) {
354 SDL_free(chosen_files_list[i]);
355 }
356
357 SDL_free(chosen_files_list);
358 }
359 } else {
360 DWORD error = pCommDlgExtendedError();
361 // Error code 0 means the user clicked the cancel button.
362 if (error == 0) {
363 /* Unlike SDL's handling of errors, Windows does reset the error
364 code to 0 after calling GetOpenFileName if another Windows
365 function before set a different error code, so it's safe to
366 check for success. */
367 const char *opts[1] = { NULL };
368 callback(userdata, opts, getFilterIndex(dialog.nFilterIndex));
369 } else {
370 SDL_SetError("Windows error, CommDlgExtendedError: %ld", pCommDlgExtendedError());
371 callback(userdata, NULL, -1);
372 }
373 }
374
375 SDL_free(filebuffer);
376}
377
378int windows_file_dialog_thread(void *ptr)
379{
380 windows_ShowFileDialog(ptr);
381 freeWinArgs(ptr);
382 return 0;
383}
384
385int CALLBACK browse_callback_proc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
386{
387 switch (uMsg) {
388 case BFFM_INITIALIZED:
389 if (lpData) {
390 SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
391 }
392 break;
393 case BFFM_SELCHANGED:
394 break;
395 case BFFM_VALIDATEFAILED:
396 break;
397 default:
398 break;
399 }
400 return 0;
401}
402
403void windows_ShowFolderDialog(void *ptr)
404{
405 winFArgs *args = (winFArgs *) ptr;
406 SDL_Window *window = args->parent;
407 SDL_DialogFileCallback callback = args->callback;
408 void *userdata = args->userdata;
409 HWND parent = NULL;
410 const char *title = args->title;
411
412 if (window) {
413 parent = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
414 }
415
416 wchar_t *title_w = NULL;
417
418 if (title) {
419 title_w = WIN_UTF8ToStringW(title);
420 if (!title_w) {
421 callback(userdata, NULL, -1);
422 return;
423 }
424 }
425
426 wchar_t buffer[MAX_PATH];
427
428 BROWSEINFOW dialog;
429 dialog.hwndOwner = parent;
430 dialog.pidlRoot = NULL;
431 dialog.pszDisplayName = buffer;
432 dialog.lpszTitle = title_w;
433 dialog.ulFlags = BIF_USENEWUI;
434 dialog.lpfn = browse_callback_proc;
435 dialog.lParam = (LPARAM)args->default_folder;
436 dialog.iImage = 0;
437
438 LPITEMIDLIST lpItem = SHBrowseForFolderW(&dialog);
439
440 SDL_free(title_w);
441
442 if (lpItem != NULL) {
443 SHGetPathFromIDListW(lpItem, buffer);
444 char *chosen_file = WIN_StringToUTF8W(buffer);
445 const char *files[2] = { chosen_file, NULL };
446 callback(userdata, (const char * const*) files, -1);
447 SDL_free(chosen_file);
448 } else {
449 const char *files[1] = { NULL };
450 callback(userdata, (const char * const*) files, -1);
451 }
452}
453
454int windows_folder_dialog_thread(void *ptr)
455{
456 windows_ShowFolderDialog(ptr);
457 freeWinFArgs((winFArgs *)ptr);
458 return 0;
459}
460
461wchar_t *win_get_filters(const SDL_DialogFileFilter *filters, int nfilters)
462{
463 wchar_t *filter_wchar = NULL;
464
465 if (filters) {
466 // '\x01' is used in place of a null byte
467 // suffix needs two null bytes in case the filter list is empty
468 char *filterlist = convert_filters(filters, nfilters, clear_filt_names,
469 "", "", "\x01\x01", "", "\x01",
470 "\x01", "*.", ";*.", "");
471
472 if (!filterlist) {
473 return NULL;
474 }
475
476 int filter_len = (int)SDL_strlen(filterlist);
477
478 for (char *c = filterlist; *c; c++) {
479 if (*c == '\x01') {
480 *c = '\0';
481 }
482 }
483
484 int filter_wlen = MultiByteToWideChar(CP_UTF8, 0, filterlist, filter_len, NULL, 0);
485 filter_wchar = (wchar_t *)SDL_malloc(filter_wlen * sizeof(wchar_t));
486 if (!filter_wchar) {
487 SDL_free(filterlist);
488 return NULL;
489 }
490
491 MultiByteToWideChar(CP_UTF8, 0, filterlist, filter_len, filter_wchar, filter_wlen);
492
493 SDL_free(filterlist);
494 }
495
496 return filter_wchar;
497}
498
499static 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)
500{
501 winArgs *args;
502 SDL_Thread *thread;
503 wchar_t *filters_str;
504
505 if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) {
506 SDL_SetError("File dialog driver unsupported");
507 callback(userdata, NULL, -1);
508 return;
509 }
510
511 args = (winArgs *)SDL_malloc(sizeof(*args));
512 if (args == NULL) {
513 callback(userdata, NULL, -1);
514 return;
515 }
516
517 filters_str = win_get_filters(filters, nfilters);
518
519 if (!filters_str && filters) {
520 callback(userdata, NULL, -1);
521 SDL_free(args);
522 return;
523 }
524
525 args->is_save = is_save;
526 args->filters_str = filters_str;
527 args->default_file = default_location ? SDL_strdup(default_location) : NULL;
528 args->parent = window;
529 args->flags = allow_many ? OFN_ALLOWMULTISELECT : 0;
530 args->callback = callback;
531 args->userdata = userdata;
532 args->title = title ? SDL_strdup(title) : NULL;
533 args->accept = accept ? SDL_strdup(accept) : NULL;
534 args->cancel = cancel ? SDL_strdup(cancel) : NULL;
535
536 thread = SDL_CreateThread(windows_file_dialog_thread, "SDL_Windows_ShowFileDialog", (void *) args);
537
538 if (thread == NULL) {
539 callback(userdata, NULL, -1);
540 // The thread won't have run, therefore the data won't have been freed
541 freeWinArgs(args);
542 return;
543 }
544
545 SDL_DetachThread(thread);
546}
547
548void 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)
549{
550 winFArgs *args;
551 SDL_Thread *thread;
552
553 if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) {
554 SDL_SetError("File dialog driver unsupported");
555 callback(userdata, NULL, -1);
556 return;
557 }
558
559 args = (winFArgs *)SDL_malloc(sizeof(*args));
560 if (args == NULL) {
561 callback(userdata, NULL, -1);
562 return;
563 }
564
565 args->parent = window;
566 args->callback = callback;
567 args->default_folder = default_location ? SDL_strdup(default_location) : NULL;
568 args->userdata = userdata;
569 args->title = title ? SDL_strdup(title) : NULL;
570 args->accept = accept ? SDL_strdup(accept) : NULL;
571 args->cancel = cancel ? SDL_strdup(cancel) : NULL;
572
573 thread = SDL_CreateThread(windows_folder_dialog_thread, "SDL_Windows_ShowFolderDialog", (void *) args);
574
575 if (thread == NULL) {
576 callback(userdata, NULL, -1);
577 // The thread won't have run, therefore the data won't have been freed
578 freeWinFArgs(args);
579 return;
580 }
581
582 SDL_DetachThread(thread);
583}
584
585void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
586{
587 /* The internal functions will start threads, and the properties may be freed as soon as this function returns.
588 Save a copy of what we need before invoking the functions and starting the threads. */
589 SDL_Window *window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL);
590 SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
591 int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0);
592 bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
593 const char *default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL);
594 const char *title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL);
595 const char *accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL);
596 const char *cancel = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_CANCEL_STRING, NULL);
597 bool is_save = false;
598
599 switch (type) {
600 case SDL_FILEDIALOG_SAVEFILE:
601 is_save = true;
602 SDL_FALLTHROUGH;
603 case SDL_FILEDIALOG_OPENFILE:
604 ShowFileDialog(callback, userdata, window, filters, nfilters, default_location, allow_many, is_save, title, accept, cancel);
605 break;
606
607 case SDL_FILEDIALOG_OPENFOLDER:
608 ShowFolderDialog(callback, userdata, window, default_location, allow_many, title, accept, cancel);
609 break;
610 };
611}