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