Fix dialogs on Windows
This fixes a bunch of issues with dialogs on Windows. - Removed lpstrFileTitle assignation, which overwrote the buffer - Increased the memory size available for long file selections - Removed seemingly unused `default_folder` in win_Args struct - Properly handle the case where only one file is selected in multiselect mode - Properly handle the initial folder, which would fail in specific conditions The details for the last entry are explained in a comment in the code.
This commit is contained in:
parent
2f8cfce154
commit
2b7af6636f
1 changed files with 77 additions and 14 deletions
|
@ -26,12 +26,14 @@
|
||||||
#include "../../core/windows/SDL_windows.h"
|
#include "../../core/windows/SDL_windows.h"
|
||||||
#include "../../thread/SDL_systhread.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
|
typedef struct
|
||||||
{
|
{
|
||||||
int is_save;
|
int is_save;
|
||||||
const SDL_DialogFileFilter *filters;
|
const SDL_DialogFileFilter *filters;
|
||||||
const char* default_file;
|
const char* default_file;
|
||||||
const char* default_folder;
|
|
||||||
SDL_Window* parent;
|
SDL_Window* parent;
|
||||||
DWORD flags;
|
DWORD flags;
|
||||||
SDL_DialogFileCallback callback;
|
SDL_DialogFileCallback callback;
|
||||||
|
@ -68,7 +70,6 @@ void windows_ShowFileDialog(void *ptr)
|
||||||
int is_save = args->is_save;
|
int is_save = args->is_save;
|
||||||
const SDL_DialogFileFilter *filters = args->filters;
|
const SDL_DialogFileFilter *filters = args->filters;
|
||||||
const char* default_file = args->default_file;
|
const char* default_file = args->default_file;
|
||||||
const char* default_folder = args->default_folder;
|
|
||||||
SDL_Window* parent = args->parent;
|
SDL_Window* parent = args->parent;
|
||||||
DWORD flags = args->flags;
|
DWORD flags = args->flags;
|
||||||
SDL_DialogFileCallback callback = args->callback;
|
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);
|
window = (HWND) SDL_GetProperty(SDL_GetWindowProperties(parent), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
wchar_t filebuffer[MAX_PATH] = L"";
|
wchar_t *filebuffer; /* lpstrFile */
|
||||||
wchar_t initfolder[MAX_PATH] = L"";
|
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 */
|
/* 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) {
|
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.
|
||||||
|
|
||||||
if (default_folder) {
|
On top of that, lpstrInitialDir hides a special algorithm that
|
||||||
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, default_folder, -1, filebuffer, MAX_PATH);
|
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);
|
||||||
|
|
||||||
|
for (int i = 0; i < SELECTLIST_SIZE; i++) {
|
||||||
|
if (filebuffer[i] == L'/') {
|
||||||
|
filebuffer[i] = L'\\';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* '\x01' is used in place of a null byte */
|
/* '\x01' is used in place of a null byte */
|
||||||
|
@ -129,6 +160,7 @@ void windows_ShowFileDialog(void *ptr)
|
||||||
|
|
||||||
if (!filterlist) {
|
if (!filterlist) {
|
||||||
callback(userdata, NULL, -1);
|
callback(userdata, NULL, -1);
|
||||||
|
SDL_free(filebuffer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,6 +179,7 @@ void windows_ShowFileDialog(void *ptr)
|
||||||
SDL_OutOfMemory();
|
SDL_OutOfMemory();
|
||||||
SDL_free(filterlist);
|
SDL_free(filterlist);
|
||||||
callback(userdata, NULL, -1);
|
callback(userdata, NULL, -1);
|
||||||
|
SDL_free(filebuffer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,9 +196,8 @@ void windows_ShowFileDialog(void *ptr)
|
||||||
dialog.nMaxCustFilter = 0;
|
dialog.nMaxCustFilter = 0;
|
||||||
dialog.nFilterIndex = 0;
|
dialog.nFilterIndex = 0;
|
||||||
dialog.lpstrFile = filebuffer;
|
dialog.lpstrFile = filebuffer;
|
||||||
dialog.nMaxFile = MAX_PATH;
|
dialog.nMaxFile = SELECTLIST_SIZE;
|
||||||
dialog.lpstrFileTitle = *filebuffer ? filebuffer : NULL;
|
dialog.lpstrFileTitle = NULL;
|
||||||
dialog.nMaxFileTitle = MAX_PATH;
|
|
||||||
dialog.lpstrInitialDir = *initfolder ? initfolder : NULL;
|
dialog.lpstrInitialDir = *initfolder ? initfolder : NULL;
|
||||||
dialog.lpstrTitle = NULL;
|
dialog.lpstrTitle = NULL;
|
||||||
dialog.Flags = flags | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_NOCHANGEDIR;
|
dialog.Flags = flags | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_NOCHANGEDIR;
|
||||||
|
@ -207,6 +239,7 @@ void windows_ShowFileDialog(void *ptr)
|
||||||
if (!chosen_files_list) {
|
if (!chosen_files_list) {
|
||||||
SDL_OutOfMemory();
|
SDL_OutOfMemory();
|
||||||
callback(userdata, NULL, -1);
|
callback(userdata, NULL, -1);
|
||||||
|
SDL_free(filebuffer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,6 +249,7 @@ void windows_ShowFileDialog(void *ptr)
|
||||||
SDL_SetError("Path too long or invalid character in path");
|
SDL_SetError("Path too long or invalid character in path");
|
||||||
SDL_free(chosen_files_list);
|
SDL_free(chosen_files_list);
|
||||||
callback(userdata, NULL, -1);
|
callback(userdata, NULL, -1);
|
||||||
|
SDL_free(filebuffer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,6 +272,7 @@ void windows_ShowFileDialog(void *ptr)
|
||||||
|
|
||||||
SDL_free(chosen_files_list);
|
SDL_free(chosen_files_list);
|
||||||
callback(userdata, NULL, -1);
|
callback(userdata, NULL, -1);
|
||||||
|
SDL_free(filebuffer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,6 +290,7 @@ void windows_ShowFileDialog(void *ptr)
|
||||||
|
|
||||||
SDL_free(chosen_files_list);
|
SDL_free(chosen_files_list);
|
||||||
callback(userdata, NULL, -1);
|
callback(userdata, NULL, -1);
|
||||||
|
SDL_free(filebuffer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,6 +307,33 @@ void windows_ShowFileDialog(void *ptr)
|
||||||
|
|
||||||
SDL_free(chosen_files_list);
|
SDL_free(chosen_files_list);
|
||||||
callback(userdata, NULL, -1);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -298,6 +361,8 @@ void windows_ShowFileDialog(void *ptr)
|
||||||
callback(userdata, NULL, -1);
|
callback(userdata, NULL, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SDL_free(filebuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
int windows_file_dialog_thread(void* ptr)
|
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->is_save = 0;
|
||||||
args->filters = filters;
|
args->filters = filters;
|
||||||
args->default_file = default_location;
|
args->default_file = default_location;
|
||||||
args->default_folder = NULL;
|
|
||||||
args->parent = window;
|
args->parent = window;
|
||||||
args->flags = (allow_many == SDL_TRUE) ? OFN_ALLOWMULTISELECT : 0;
|
args->flags = (allow_many == SDL_TRUE) ? OFN_ALLOWMULTISELECT : 0;
|
||||||
args->callback = callback;
|
args->callback = callback;
|
||||||
|
@ -438,7 +502,6 @@ void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL
|
||||||
args->is_save = 1;
|
args->is_save = 1;
|
||||||
args->filters = filters;
|
args->filters = filters;
|
||||||
args->default_file = default_location;
|
args->default_file = default_location;
|
||||||
args->default_folder = NULL;
|
|
||||||
args->parent = window;
|
args->parent = window;
|
||||||
args->flags = 0;
|
args->flags = 0;
|
||||||
args->callback = callback;
|
args->callback = callback;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue