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:
Semphris 2024-04-02 16:03:58 -04:00 committed by Sam Lantinga
parent 5fa87e29e7
commit 6ad390fc50
18 changed files with 501 additions and 125 deletions

View File

@ -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)

View File

@ -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" />

View File

@ -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>

View File

@ -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" />

View File

@ -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>

View File

@ -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" />

View File

@ -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>

View File

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

View File

@ -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

View File

@ -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()
*

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

View 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);

View File

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

View File

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

View File

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

View File

@ -27,31 +27,56 @@ static void (*detected_open)(SDL_DialogFileCallback callback, void* userdata, SD
static void (*detected_save)(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location) = NULL;
static void (*detected_folder)(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, SDL_bool allow_many) = NULL;
/* Returns non-zero on success, 0 on failure */
static int detect_available_methods(void)
static int detect_available_methods(const char *value);
void SDLCALL hint_callback(void *userdata, const char *name, const char *oldValue, const char *newValue)
{
if (SDL_Portal_detect()) {
detected_open = SDL_Portal_ShowOpenFileDialog;
detected_save = SDL_Portal_ShowSaveFileDialog;
detected_folder = SDL_Portal_ShowOpenFolderDialog;
return 1;
detect_available_methods(newValue);
}
static void set_callback(void)
{
static SDL_bool is_set = SDL_FALSE;
if (is_set == SDL_FALSE) {
is_set = SDL_TRUE;
SDL_AddHintCallback(SDL_HINT_FILE_DIALOG_DRIVER, hint_callback, NULL);
}
}
/* Returns non-zero on success, 0 on failure */
static int detect_available_methods(const char *value)
{
const char *driver = value ? value : SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER);
set_callback();
if (driver == NULL || SDL_strcmp(driver, "portal") == 0) {
if (SDL_Portal_detect()) {
detected_open = SDL_Portal_ShowOpenFileDialog;
detected_save = SDL_Portal_ShowSaveFileDialog;
detected_folder = SDL_Portal_ShowOpenFolderDialog;
return 1;
}
}
if (SDL_Zenity_detect()) {
detected_open = SDL_Zenity_ShowOpenFileDialog;
detected_save = SDL_Zenity_ShowSaveFileDialog;
detected_folder = SDL_Zenity_ShowOpenFolderDialog;
return 2;
if (driver == NULL || SDL_strcmp(driver, "zenity") == 0) {
if (SDL_Zenity_detect()) {
detected_open = SDL_Zenity_ShowOpenFileDialog;
detected_save = SDL_Zenity_ShowSaveFileDialog;
detected_folder = SDL_Zenity_ShowOpenFolderDialog;
return 2;
}
}
SDL_SetError("No supported method for file dialogs");
SDL_SetError("File dialog driver unsupported");
return 0;
}
void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location, SDL_bool allow_many)
{
/* Call detect_available_methods() again each time in case the situation changed */
if (!detected_open && !detect_available_methods()) {
if (!detected_open && !detect_available_methods(NULL)) {
/* SetError() done by detect_available_methods() */
callback(userdata, NULL, -1);
return;
@ -63,7 +88,7 @@ void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL
void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location)
{
/* Call detect_available_methods() again each time in case the situation changed */
if (!detected_save && !detect_available_methods()) {
if (!detected_save && !detect_available_methods(NULL)) {
/* SetError() done by detect_available_methods() */
callback(userdata, NULL, -1);
return;
@ -75,7 +100,7 @@ void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL
void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, SDL_bool allow_many)
{
/* Call detect_available_methods() again each time in case the situation changed */
if (!detected_folder && !detect_available_methods()) {
if (!detected_folder && !detect_available_methods(NULL)) {
/* SetError() done by detect_available_methods() */
callback(userdata, NULL, -1);
return;

View File

@ -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);
CLEAR_AND_RETURN()
}
if (i_buf + 3 >= 4095) {
i_buf += 3;
break;
}
buffer[i_buf++] = ' ';
buffer[i_buf++] = '*';
buffer[i_buf++] = '.';
} else if (*c == '*' && (c[1] == '\0' || c[1] == ';') && (i_pat == 0 || *(c - 1) == ';')) {
buffer[i_buf++] = '*';
} else if (!((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') || (*c >= '0' && *c <= '9') || *c == '.' || *c == '_' || *c == '-')) {
SDL_SetError("Illegal character in pattern name: %c (Only alphanumeric characters, periods, underscores and hyphens allowed)", *c);
CLEAR_AND_RETURN()
} else {
buffer[i_buf++] = *c;
}
}
if (i_buf >= 4095) {
SDL_SetError("Filter '%s' wouldn't fit in a 4096 byte buffer; please report your use case if you need filters that long", filter_ptr->name);
if (!filter_str) {
CLEAR_AND_RETURN()
}
buffer[i_buf] = '\0';
argv[nextarg++] = SDL_strdup(buffer);
argv[nextarg++] = filter_str;
CHECK_OOM()
filter_ptr++;

View File

@ -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;
*filter_ptr++ = L'*';
*filter_ptr++ = L'.';
while (*pattern_ptr) {
if (*pattern_ptr == ';') {
*filter_ptr++ = L';';
*filter_ptr++ = L'*';
*filter_ptr++ = L'.';
} else if (*pattern_ptr == '*' && (pattern_ptr[1] == '\0' || pattern_ptr[1] == ';')) {
*filter_ptr++ = L'*';
} else if (!((*pattern_ptr >= 'a' && *pattern_ptr <= 'z') || (*pattern_ptr >= 'A' && *pattern_ptr <= 'Z') || (*pattern_ptr >= '0' && *pattern_ptr <= '9') || *pattern_ptr == '.' || *pattern_ptr == '_' || *pattern_ptr == '-')) {
SDL_SetError("Illegal character in pattern name: %c (Only alphanumeric characters, periods, underscores and hyphens allowed)", *pattern_ptr);
callback(userdata, NULL, -1);
} else {
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, pattern_ptr, 1, filter_ptr, 1);
filter_ptr++;
}
pattern_ptr++;
for (char *c = filterlist; *c; c++) {
if (*c == '\x01') {
*c = '\0';
}
*filter_ptr++ = '\0';
}
*filter_ptr = '\0';
int filter_wlen = MultiByteToWideChar(CP_UTF8, 0, filterlist, filter_len, NULL, 0);
wchar_t *filter_wchar = SDL_malloc(filter_wlen * sizeof(wchar_t));
if (!filter_wchar) {
SDL_OutOfMemory();
SDL_free(filterlist);
callback(userdata, NULL, -1);
return;
}
MultiByteToWideChar(CP_UTF8, 0, filterlist, filter_len, filter_wchar, filter_wlen);
SDL_free(filterlist);
OPENFILENAMEW dialog;
dialog.lStructSize = sizeof(OPENFILENAME);
dialog.hwndOwner = window;
dialog.hInstance = 0;
dialog.lpstrFilter = filterlist;
dialog.lpstrFilter = filter_wchar;
dialog.lpstrCustomFilter = NULL;
dialog.nMaxCustFilter = 0;
dialog.nFilterIndex = 0;
@ -198,7 +180,7 @@ void windows_ShowFileDialog(void *ptr)
BOOL result = pGetAnyFileName(&dialog);
SDL_free(filterlist);
SDL_free(filter_wchar);
if (result) {
if (!(flags & OFN_ALLOWMULTISELECT)) {
@ -401,6 +383,13 @@ void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL
winArgs *args;
SDL_Thread *thread;
if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) {
SDL_Log("%s", SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER));
SDL_SetError("File dialog driver unsupported");
callback(userdata, NULL, -1);
return;
}
args = SDL_malloc(sizeof(winArgs));
if (args == NULL) {
SDL_OutOfMemory();
@ -421,6 +410,7 @@ void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL
if (thread == NULL) {
callback(userdata, NULL, -1);
SDL_free(args);
return;
}
@ -432,6 +422,12 @@ void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL
winArgs *args;
SDL_Thread *thread;
if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) {
SDL_SetError("File dialog driver unsupported");
callback(userdata, NULL, -1);
return;
}
args = SDL_malloc(sizeof(winArgs));
if (args == NULL) {
SDL_OutOfMemory();
@ -452,6 +448,7 @@ void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL
if (thread == NULL) {
callback(userdata, NULL, -1);
SDL_free(args);
return;
}
@ -463,6 +460,12 @@ void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, S
winFArgs *args;
SDL_Thread *thread;
if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) {
SDL_SetError("File dialog driver unsupported");
callback(userdata, NULL, -1);
return;
}
args = SDL_malloc(sizeof(winFArgs));
if (args == NULL) {
SDL_OutOfMemory();
@ -479,6 +482,7 @@ void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, S
if (thread == NULL) {
callback(userdata, NULL, -1);
SDL_free(args);
return;
}