diff options
Diffstat (limited to 'contrib/SDL-3.2.8/src/dialog')
| -rw-r--r-- | contrib/SDL-3.2.8/src/dialog/SDL_dialog.c | 131 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/dialog/SDL_dialog.h | 22 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/dialog/SDL_dialog_utils.c | 256 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/dialog/SDL_dialog_utils.h | 59 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/dialog/android/SDL_androiddialog.c | 58 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/dialog/cocoa/SDL_cocoadialog.m | 188 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/dialog/dummy/SDL_dummydialog.c | 33 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/dialog/haiku/SDL_haikudialog.cc | 293 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/dialog/unix/SDL_portaldialog.c | 545 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/dialog/unix/SDL_portaldialog.h | 27 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/dialog/unix/SDL_unixdialog.c | 81 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/dialog/unix/SDL_zenitydialog.c | 366 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/dialog/unix/SDL_zenitydialog.h | 27 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/dialog/windows/SDL_windowsdialog.c | 611 |
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 | |||
| 26 | void 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 | |||
| 67 | void 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 | |||
| 90 | void 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 | |||
| 112 | void 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 | |||
| 22 | void 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 | |||
| 25 | char *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 | |||
| 97 | char *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 | |||
| 147 | char *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 | |||
| 216 | const 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 | |||
| 231 | const 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). */ | ||
| 31 | typedef char *(NameTransform)(const char * name); | ||
| 32 | |||
| 33 | // Converts all the filters into a single string. | ||
| 34 | // <prefix>[filter]{<separator>[filter]...}<suffix> | ||
| 35 | char *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> | ||
| 44 | char *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> | ||
| 51 | char *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 | ||
| 56 | const char *validate_filters(const SDL_DialogFileFilter *filters, | ||
| 57 | int nfilters); | ||
| 58 | |||
| 59 | const 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 | |||
| 26 | void 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 | |||
| 30 | void 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 | |||
| 27 | void 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" | ||
| 22 | extern "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 | |||
| 41 | bool 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 | |||
| 46 | std::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 | |||
| 62 | class SDLBRefFilter : public BRefFilter | ||
| 63 | { | ||
| 64 | public: | ||
| 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 | |||
| 98 | private: | ||
| 99 | const SDL_DialogFileFilter * const m_filters; | ||
| 100 | int m_nfilters; | ||
| 101 | }; | ||
| 102 | |||
| 103 | class CallbackLooper : public BLooper | ||
| 104 | { | ||
| 105 | public: | ||
| 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 | |||
| 184 | private: | ||
| 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 | |||
| 195 | void 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 | |||
| 47 | typedef struct { | ||
| 48 | SDL_DialogFileCallback callback; | ||
| 49 | void *userdata; | ||
| 50 | const char *path; | ||
| 51 | } SignalCallback; | ||
| 52 | |||
| 53 | static 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 | |||
| 65 | static 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 | |||
| 77 | static 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 | |||
| 116 | cleanup: | ||
| 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 | |||
| 123 | static 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 | |||
| 140 | static 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 | |||
| 156 | static 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 | ||
| 268 | done: | ||
| 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 | |||
| 282 | not_our_signal: | ||
| 283 | return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; | ||
| 284 | } | ||
| 285 | |||
| 286 | void 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, ¶ms); | ||
| 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(¶ms, 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(¶ms, DBUS_TYPE_STRING, &method_title); | ||
| 391 | dbus->message_iter_open_container(¶ms, 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(¶ms, &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 | |||
| 470 | incorrect_type: | ||
| 471 | dbus->message_unref(reply); | ||
| 472 | } | ||
| 473 | |||
| 474 | bool 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 | |||
| 522 | done: | ||
| 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 | |||
| 534 | void 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 | |||
| 540 | bool 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 | |||
| 24 | void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props); | ||
| 25 | |||
| 26 | /** @returns non-zero if available, zero if unavailable */ | ||
| 27 | bool 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 | |||
| 27 | static void (*detected_function)(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) = NULL; | ||
| 28 | |||
| 29 | void SDLCALL hint_callback(void *userdata, const char *name, const char *oldValue, const char *newValue); | ||
| 30 | |||
| 31 | static 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 | ||
| 42 | static 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 | |||
| 66 | void SDLCALL hint_callback(void *userdata, const char *name, const char *oldValue, const char *newValue) | ||
| 67 | { | ||
| 68 | detect_available_methods(newValue); | ||
| 69 | } | ||
| 70 | |||
| 71 | void 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 | ||
| 26 | typedef 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 | |||
| 43 | static 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 | |||
| 59 | static 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 | */ | ||
| 88 | static 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 | |||
| 206 | cleanup: | ||
| 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 | |||
| 218 | static 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 | |||
| 294 | done: | ||
| 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 | |||
| 305 | static 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 | |||
| 320 | static 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 | |||
| 328 | void 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 | |||
| 347 | bool 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 | |||
| 24 | extern 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 */ | ||
| 27 | extern 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 | |||
| 34 | typedef 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 | |||
| 48 | typedef 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 | |||
| 59 | void 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 | |||
| 70 | void 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 */ | ||
| 81 | int getFilterIndex(int as_reported_by_windows) | ||
| 82 | { | ||
| 83 | return as_reported_by_windows - 1; | ||
| 84 | } | ||
| 85 | |||
| 86 | char *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 | ||
| 105 | void 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 | |||
| 378 | int windows_file_dialog_thread(void *ptr) | ||
| 379 | { | ||
| 380 | windows_ShowFileDialog(ptr); | ||
| 381 | freeWinArgs(ptr); | ||
| 382 | return 0; | ||
| 383 | } | ||
| 384 | |||
| 385 | int 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 | |||
| 403 | void 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 | |||
| 454 | int windows_folder_dialog_thread(void *ptr) | ||
| 455 | { | ||
| 456 | windows_ShowFolderDialog(ptr); | ||
| 457 | freeWinFArgs((winFArgs *)ptr); | ||
| 458 | return 0; | ||
| 459 | } | ||
| 460 | |||
| 461 | wchar_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 | |||
| 499 | static void ShowFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location, bool allow_many, bool is_save, const char *title, const char *accept, const char *cancel) | ||
| 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 | |||
| 548 | void ShowFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const char *default_location, bool allow_many, const char *title, const char *accept, const char *cancel) | ||
| 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 | |||
| 585 | void 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 | } | ||
