|
@@ -26,12 +26,14 @@
|
|
|
#include "../../core/windows/SDL_windows.h"
|
|
|
#include "../../thread/SDL_systhread.h"
|
|
|
|
|
|
+/* If this number is too small, selecting too many files will give an error */
|
|
|
+#define SELECTLIST_SIZE 65536
|
|
|
+
|
|
|
typedef struct
|
|
|
{
|
|
|
int is_save;
|
|
|
const SDL_DialogFileFilter *filters;
|
|
|
const char* default_file;
|
|
|
- const char* default_folder;
|
|
|
SDL_Window* parent;
|
|
|
DWORD flags;
|
|
|
SDL_DialogFileCallback callback;
|
|
@@ -68,7 +70,6 @@ void windows_ShowFileDialog(void *ptr)
|
|
|
int is_save = args->is_save;
|
|
|
const SDL_DialogFileFilter *filters = args->filters;
|
|
|
const char* default_file = args->default_file;
|
|
|
- const char* default_folder = args->default_folder;
|
|
|
SDL_Window* parent = args->parent;
|
|
|
DWORD flags = args->flags;
|
|
|
SDL_DialogFileCallback callback = args->callback;
|
|
@@ -109,18 +110,48 @@ void windows_ShowFileDialog(void *ptr)
|
|
|
window = (HWND) SDL_GetProperty(SDL_GetWindowProperties(parent), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
|
|
|
}
|
|
|
|
|
|
- wchar_t filebuffer[MAX_PATH] = L"";
|
|
|
- wchar_t initfolder[MAX_PATH] = L"";
|
|
|
+ wchar_t *filebuffer; /* lpstrFile */
|
|
|
+ wchar_t initfolder[MAX_PATH] = L""; /* lpstrInitialDir */
|
|
|
+
|
|
|
+ /* If SELECTLIST_SIZE is too large, putting filebuffer on the stack might
|
|
|
+ cause an overflow */
|
|
|
+ filebuffer = (wchar_t *) SDL_malloc(SELECTLIST_SIZE * sizeof(wchar_t));
|
|
|
|
|
|
/* Necessary for the return code below */
|
|
|
- SDL_memset(filebuffer, 0, MAX_PATH * sizeof(wchar_t));
|
|
|
+ SDL_memset(filebuffer, 0, SELECTLIST_SIZE * sizeof(wchar_t));
|
|
|
|
|
|
if (default_file) {
|
|
|
- MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, default_file, -1, filebuffer, MAX_PATH);
|
|
|
- }
|
|
|
+ /* On Windows 10, 11 and possibly others, lpstrFile can be initialized
|
|
|
+ with a path and the dialog will start at that location, but *only if
|
|
|
+ the path contains a filename*. If it ends with a folder (directory
|
|
|
+ separator), it fails with 0x3002 (12290) FNERR_INVALIDFILENAME. For
|
|
|
+ that specific case, lpstrInitialDir must be used instead, but just
|
|
|
+ for that case, because lpstrInitialDir doesn't support file names.
|
|
|
+
|
|
|
+ On top of that, lpstrInitialDir hides a special algorithm that
|
|
|
+ decides which folder to actually use as starting point, which may or
|
|
|
+ may not be the one provided, or some other unrelated folder. Also,
|
|
|
+ the algorithm changes between platforms. Assuming the documentation
|
|
|
+ is correct, the algorithm is there under 'lpstrInitialDir':
|
|
|
+
|
|
|
+ https://learn.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-openfilenamew
|
|
|
+
|
|
|
+ Finally, lpstrFile does not support forward slashes. lpstrInitialDir
|
|
|
+ does, though. */
|
|
|
+
|
|
|
+ char last_c = default_file[SDL_strlen(default_file) - 1];
|
|
|
+
|
|
|
+ if (last_c == '\\' || last_c == '/') {
|
|
|
+ MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, default_file, -1, initfolder, MAX_PATH);
|
|
|
+ } else {
|
|
|
+ MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, default_file, -1, filebuffer, MAX_PATH);
|
|
|
|
|
|
- if (default_folder) {
|
|
|
- MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, default_folder, -1, filebuffer, MAX_PATH);
|
|
|
+ for (int i = 0; i < SELECTLIST_SIZE; i++) {
|
|
|
+ if (filebuffer[i] == L'/') {
|
|
|
+ filebuffer[i] = L'\\';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/* '\x01' is used in place of a null byte */
|
|
@@ -129,6 +160,7 @@ void windows_ShowFileDialog(void *ptr)
|
|
|
|
|
|
if (!filterlist) {
|
|
|
callback(userdata, NULL, -1);
|
|
|
+ SDL_free(filebuffer);
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -147,6 +179,7 @@ void windows_ShowFileDialog(void *ptr)
|
|
|
SDL_OutOfMemory();
|
|
|
SDL_free(filterlist);
|
|
|
callback(userdata, NULL, -1);
|
|
|
+ SDL_free(filebuffer);
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -163,9 +196,8 @@ void windows_ShowFileDialog(void *ptr)
|
|
|
dialog.nMaxCustFilter = 0;
|
|
|
dialog.nFilterIndex = 0;
|
|
|
dialog.lpstrFile = filebuffer;
|
|
|
- dialog.nMaxFile = MAX_PATH;
|
|
|
- dialog.lpstrFileTitle = *filebuffer ? filebuffer : NULL;
|
|
|
- dialog.nMaxFileTitle = MAX_PATH;
|
|
|
+ dialog.nMaxFile = SELECTLIST_SIZE;
|
|
|
+ dialog.lpstrFileTitle = NULL;
|
|
|
dialog.lpstrInitialDir = *initfolder ? initfolder : NULL;
|
|
|
dialog.lpstrTitle = NULL;
|
|
|
dialog.Flags = flags | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_NOCHANGEDIR;
|
|
@@ -207,6 +239,7 @@ void windows_ShowFileDialog(void *ptr)
|
|
|
if (!chosen_files_list) {
|
|
|
SDL_OutOfMemory();
|
|
|
callback(userdata, NULL, -1);
|
|
|
+ SDL_free(filebuffer);
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -216,6 +249,7 @@ void windows_ShowFileDialog(void *ptr)
|
|
|
SDL_SetError("Path too long or invalid character in path");
|
|
|
SDL_free(chosen_files_list);
|
|
|
callback(userdata, NULL, -1);
|
|
|
+ SDL_free(filebuffer);
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -238,6 +272,7 @@ void windows_ShowFileDialog(void *ptr)
|
|
|
|
|
|
SDL_free(chosen_files_list);
|
|
|
callback(userdata, NULL, -1);
|
|
|
+ SDL_free(filebuffer);
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -255,6 +290,7 @@ void windows_ShowFileDialog(void *ptr)
|
|
|
|
|
|
SDL_free(chosen_files_list);
|
|
|
callback(userdata, NULL, -1);
|
|
|
+ SDL_free(filebuffer);
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -271,6 +307,33 @@ void windows_ShowFileDialog(void *ptr)
|
|
|
|
|
|
SDL_free(chosen_files_list);
|
|
|
callback(userdata, NULL, -1);
|
|
|
+ SDL_free(filebuffer);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* If the user chose only one file, it's all just one string */
|
|
|
+ if (nfiles == 0) {
|
|
|
+ nfiles++;
|
|
|
+ char **new_cfl = (char **) SDL_realloc(chosen_files_list, sizeof(char*) * (nfiles + 1));
|
|
|
+
|
|
|
+ if (!new_cfl) {
|
|
|
+ SDL_OutOfMemory();
|
|
|
+ SDL_free(chosen_files_list);
|
|
|
+ callback(userdata, NULL, -1);
|
|
|
+ SDL_free(filebuffer);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ chosen_files_list = new_cfl;
|
|
|
+ chosen_files_list[nfiles] = NULL;
|
|
|
+ chosen_files_list[nfiles - 1] = SDL_strdup(chosen_folder);
|
|
|
+
|
|
|
+ if (!chosen_files_list[nfiles - 1]) {
|
|
|
+ SDL_OutOfMemory();
|
|
|
+ SDL_free(chosen_files_list);
|
|
|
+ callback(userdata, NULL, -1);
|
|
|
+ SDL_free(filebuffer);
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
@@ -298,6 +361,8 @@ void windows_ShowFileDialog(void *ptr)
|
|
|
callback(userdata, NULL, -1);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ SDL_free(filebuffer);
|
|
|
}
|
|
|
|
|
|
int windows_file_dialog_thread(void* ptr)
|
|
@@ -400,7 +465,6 @@ void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL
|
|
|
args->is_save = 0;
|
|
|
args->filters = filters;
|
|
|
args->default_file = default_location;
|
|
|
- args->default_folder = NULL;
|
|
|
args->parent = window;
|
|
|
args->flags = (allow_many == SDL_TRUE) ? OFN_ALLOWMULTISELECT : 0;
|
|
|
args->callback = callback;
|
|
@@ -438,7 +502,6 @@ void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL
|
|
|
args->is_save = 1;
|
|
|
args->filters = filters;
|
|
|
args->default_file = default_location;
|
|
|
- args->default_folder = NULL;
|
|
|
args->parent = window;
|
|
|
args->flags = 0;
|
|
|
args->callback = callback;
|