Atlas - SDL_windowsdialog.c
Home / ext / SDL / src / dialog / windows Lines: 1 | Size: 47188 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 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 "../../core/windows/SDL_windows.h" 23#include "../SDL_dialog.h" 24#include "../SDL_dialog_utils.h" 25 26#include <unknwn.h> 27#include <commdlg.h> 28#include <shlobj.h> 29#include <shobjidl.h> 30#include "../../thread/SDL_systhread.h" 31 32#if WINVER < _WIN32_WINNT_VISTA 33typedef struct _COMDLG_FILTERSPEC 34{ 35 LPCWSTR pszName; 36 LPCWSTR pszSpec; 37} COMDLG_FILTERSPEC; 38 39typedef enum FDE_OVERWRITE_RESPONSE 40{ 41 FDEOR_DEFAULT, 42 FDEOR_ACCEPT, 43 FDEOR_REFUSE, 44} FDE_OVERWRITE_RESPONSE; 45 46typedef enum FDE_SHAREVIOLATION_RESPONSE 47{ 48 FDESVR_DEFAULT, 49 FDESVR_ACCEPT, 50 FDESVR_REFUSE, 51} FDE_SHAREVIOLATION_RESPONSE; 52 53typedef enum FDAP 54{ 55 FDAP_BOTTOM, 56 FDAP_TOP, 57} FDAP; 58 59typedef ULONG SFGAOF; 60typedef DWORD SHCONTF; 61 62#endif // WINVER < _WIN32_WINNT_VISTA 63 64#ifndef __IFileDialog_FWD_DEFINED__ 65typedef struct IFileDialog IFileDialog; 66#endif 67#ifndef __IShellItem_FWD_DEFINED__ 68typedef struct IShellItem IShellItem; 69#endif 70#ifndef __IFileOpenDialog_FWD_DEFINED__ 71typedef struct IFileOpenDialog IFileOpenDialog; 72#endif 73#ifndef __IFileDialogEvents_FWD_DEFINED__ 74typedef struct IFileDialogEvents IFileDialogEvents; 75#endif 76#ifndef __IShellItemArray_FWD_DEFINED__ 77typedef struct IShellItemArray IShellItemArray; 78#endif 79#ifndef __IEnumShellItems_FWD_DEFINED__ 80typedef struct IEnumShellItems IEnumShellItems; 81#endif 82#ifndef __IShellItemFilter_FWD_DEFINED__ 83typedef struct IShellItemFilter IShellItemFilter; 84#endif 85#ifndef __IFileDialog2_FWD_DEFINED__ 86typedef struct IFileDialog2 IFileDialog2; 87#endif 88 89#ifndef __IShellItemFilter_INTERFACE_DEFINED__ 90typedef struct IShellItemFilterVtbl 91{ 92 HRESULT (__stdcall *QueryInterface)(IShellItemFilter *This, REFIID riid, void **ppvObject); 93 ULONG (__stdcall *AddRef)(IShellItemFilter *This); 94 ULONG (__stdcall *Release)(IShellItemFilter *This); 95 HRESULT (__stdcall *IncludeItem)(IShellItemFilter *This, IShellItem *psi); 96 HRESULT (__stdcall *GetEnumFlagsForItem)(IShellItemFilter *This, IShellItem *psi, SHCONTF *pgrfFlags); 97} IShellItemFilterVtbl; 98 99struct IShellItemFilter 100{ 101 IShellItemFilterVtbl *lpVtbl; 102}; 103 104#endif // #ifndef __IShellItemFilter_INTERFACE_DEFINED__ 105 106#ifndef __IFileDialogEvents_INTERFACE_DEFINED__ 107typedef struct IFileDialogEventsVtbl 108{ 109 HRESULT (__stdcall *QueryInterface)(IFileDialogEvents *This, REFIID riid, void **ppvObject); 110 ULONG (__stdcall *AddRef)(IFileDialogEvents *This); 111 ULONG (__stdcall *Release)(IFileDialogEvents *This); 112 HRESULT (__stdcall *OnFileOk)(IFileDialogEvents *This, IFileDialog *pfd); 113 HRESULT (__stdcall *OnFolderChanging)(IFileDialogEvents *This, IFileDialog *pfd, IShellItem *psiFolder); 114 HRESULT (__stdcall *OnFolderChange)(IFileDialogEvents *This, IFileDialog *pfd); 115 HRESULT (__stdcall *OnSelectionChange)(IFileDialogEvents *This, IFileDialog *pfd); 116 HRESULT (__stdcall *OnShareViolation)(IFileDialogEvents *This, IFileDialog *pfd, IShellItem *psi, FDE_SHAREVIOLATION_RESPONSE *pResponse); 117 HRESULT (__stdcall *OnTypeChange)(IFileDialogEvents *This, IFileDialog *pfd); 118 HRESULT (__stdcall *OnOverwrite)(IFileDialogEvents *This, IFileDialog *pfd, IShellItem *psi, FDE_OVERWRITE_RESPONSE *pResponse); 119} IFileDialogEventsVtbl; 120 121struct IFileDialogEvents 122{ 123 IFileDialogEventsVtbl *lpVtbl; 124}; 125 126#endif // #ifndef __IFileDialogEvents_INTERFACE_DEFINED__ 127 128#ifndef __IShellItem_INTERFACE_DEFINED__ 129typedef enum _SIGDN { 130 SIGDN_NORMALDISPLAY = 0x00000000, 131 SIGDN_PARENTRELATIVEPARSING = 0x80018001, 132 SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000, 133 SIGDN_PARENTRELATIVEEDITING = 0x80031001, 134 SIGDN_DESKTOPABSOLUTEEDITING = 0x8004C000, 135 SIGDN_FILESYSPATH = 0x80058000, 136 SIGDN_URL = 0x80068000, 137 SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007C001, 138 SIGDN_PARENTRELATIVE = 0x80080001, 139 SIGDN_PARENTRELATIVEFORUI = 0x80094001 140} SIGDN; 141 142enum _SICHINTF { 143 SICHINT_DISPLAY = 0x00000000, 144 SICHINT_ALLFIELDS = 0x80000000, 145 SICHINT_CANONICAL = 0x10000000, 146 SICHINT_TEST_FILESYSPATH_IF_NOT_EQUAL = 0x20000000 147}; 148typedef DWORD SICHINTF; 149 150extern const IID IID_IShellItem; 151 152typedef struct IShellItemVtbl 153{ 154 HRESULT (__stdcall *QueryInterface)(IShellItem *This, REFIID riid, void **ppvObject); 155 ULONG (__stdcall *AddRef)(IShellItem *This); 156 ULONG (__stdcall *Release)(IShellItem *This); 157 HRESULT (__stdcall *BindToHandler)(IShellItem *This, IBindCtx *pbc, REFGUID bhid, REFIID riid, void **ppv); 158 HRESULT (__stdcall *GetParent)(IShellItem *This, IShellItem **ppsi); 159 HRESULT (__stdcall *GetDisplayName)(IShellItem *This, SIGDN sigdnName, LPWSTR *ppszName); 160 HRESULT (__stdcall *GetAttributes)(IShellItem *This, SFGAOF sfgaoMask, SFGAOF *psfgaoAttribs); 161 HRESULT (__stdcall *Compare)(IShellItem *This, IShellItem *psi, SICHINTF hint, int *piOrder); 162} IShellItemVtbl; 163 164struct IShellItem 165{ 166 IShellItemVtbl *lpVtbl; 167}; 168 169#endif // #ifndef __IShellItem_INTERFACE_DEFINED__ 170 171#ifndef __IEnumShellItems_INTERFACE_DEFINED__ 172typedef struct IEnumShellItemsVtbl 173{ 174 HRESULT (__stdcall *QueryInterface)(IEnumShellItems *This, REFIID riid, void **ppvObject); 175 ULONG (__stdcall *AddRef)(IEnumShellItems *This); 176 ULONG (__stdcall *Release)(IEnumShellItems *This); 177 HRESULT (__stdcall *Next)(IEnumShellItems *This, ULONG celt, IShellItem **rgelt, ULONG *pceltFetched); 178 HRESULT (__stdcall *Skip)(IEnumShellItems *This, ULONG celt); 179 HRESULT (__stdcall *Reset)(IEnumShellItems *This); 180 HRESULT (__stdcall *Clone)(IEnumShellItems *This, IEnumShellItems **ppenum); 181} IEnumShellItemsVtbl; 182 183struct IEnumShellItems 184{ 185 IEnumShellItemsVtbl *lpVtbl; 186}; 187 188#endif // #ifndef __IEnumShellItems_INTERFACE_DEFINED__ 189 190#ifndef __IShellItemArray_INTERFACE_DEFINED__ 191typedef enum SIATTRIBFLAGS 192{ 193 SIATTRIBFLAGS_AND = 0x1, 194 SIATTRIBFLAGS_OR = 0x2, 195 SIATTRIBFLAGS_APPCOMPAT = 0x3, 196 SIATTRIBFLAGS_MASK = 0x3, 197 SIATTRIBFLAGS_ALLITEMS = 0x4000 198} SIATTRIBFLAGS; 199 200typedef struct IShellItemArrayVtbl 201{ 202 HRESULT (__stdcall *QueryInterface)(IShellItemArray *This, REFIID riid, void **ppvObject); 203 ULONG (__stdcall *AddRef)(IShellItemArray *This); 204 ULONG (__stdcall *Release)(IShellItemArray *This); 205 HRESULT (__stdcall *BindToHandler)(IShellItemArray *This, IBindCtx *pbc, REFGUID bhid, REFIID riid, void **ppvOut); 206 HRESULT (__stdcall *GetPropertyStore)(IShellItemArray *This, GETPROPERTYSTOREFLAGS flags, REFIID riid, void **ppv); 207 HRESULT (__stdcall *GetPropertyDescriptionList)(IShellItemArray *This, REFPROPERTYKEY keyType, REFIID riid, void **ppv); 208 HRESULT (__stdcall *GetAttributes)(IShellItemArray *This, SIATTRIBFLAGS AttribFlags, SFGAOF sfgaoMask, SFGAOF *psfgaoAttribs); 209 HRESULT (__stdcall *GetCount)(IShellItemArray *This, DWORD *pdwNumItems); 210 HRESULT (__stdcall *GetItemAt)(IShellItemArray *This, DWORD dwIndex, IShellItem **ppsi); 211 HRESULT (__stdcall *EnumItems)(IShellItemArray *This, IEnumShellItems **ppenumShellItems); 212} IShellItemArrayVtbl; 213 214struct IShellItemArray 215{ 216 IShellItemArrayVtbl *lpVtbl; 217}; 218 219#endif // #ifndef __IShellItemArray_INTERFACE_DEFINED__ 220 221// Flags/GUIDs defined for compatibility with pre-Vista headers 222#ifndef __IFileDialog_INTERFACE_DEFINED__ 223enum _FILEOPENDIALOGOPTIONS 224{ 225 FOS_OVERWRITEPROMPT = 0x2, 226 FOS_STRICTFILETYPES = 0x4, 227 FOS_NOCHANGEDIR = 0x8, 228 FOS_PICKFOLDERS = 0x20, 229 FOS_FORCEFILESYSTEM = 0x40, 230 FOS_ALLNONSTORAGEITEMS = 0x80, 231 FOS_NOVALIDATE = 0x100, 232 FOS_ALLOWMULTISELECT = 0x200, 233 FOS_PATHMUSTEXIST = 0x800, 234 FOS_FILEMUSTEXIST = 0x1000, 235 FOS_CREATEPROMPT = 0x2000, 236 FOS_SHAREAWARE = 0x4000, 237 FOS_NOREADONLYRETURN = 0x8000, 238 FOS_NOTESTFILECREATE = 0x10000, 239 FOS_HIDEMRUPLACES = 0x20000, 240 FOS_HIDEPINNEDPLACES = 0x40000, 241 FOS_NODEREFERENCELINKS = 0x100000, 242 FOS_OKBUTTONNEEDSINTERACTION = 0x200000, 243 FOS_DONTADDTORECENT = 0x2000000, 244 FOS_FORCESHOWHIDDEN = 0x10000000, 245 FOS_DEFAULTNOMINIMODE = 0x20000000, 246 FOS_FORCEPREVIEWPANEON = 0x40000000, 247 FOS_SUPPORTSTREAMABLEITEMS = 0x80000000 248}; 249 250typedef DWORD FILEOPENDIALOGOPTIONS; 251 252extern const IID IID_IFileDialog; 253 254typedef struct IFileDialogVtbl 255{ 256 HRESULT (__stdcall *QueryInterface)(IFileDialog *This, REFIID riid, void **ppvObject); 257 ULONG (__stdcall *AddRef)(IFileDialog *This); 258 ULONG (__stdcall *Release)(IFileDialog *This); 259 HRESULT (__stdcall *Show)(IFileDialog *This, HWND hwndOwner); 260 HRESULT (__stdcall *SetFileTypes)(IFileDialog *This, UINT cFileTypes, const COMDLG_FILTERSPEC *rgFilterSpec); 261 HRESULT (__stdcall *SetFileTypeIndex)(IFileDialog *This, UINT iFileType); 262 HRESULT (__stdcall *GetFileTypeIndex)(IFileDialog *This, UINT *piFileType); 263 HRESULT (__stdcall *Advise)(IFileDialog *This, IFileDialogEvents *pfde, DWORD *pdwCookie); 264 HRESULT (__stdcall *Unadvise)(IFileDialog *This, DWORD dwCookie); 265 HRESULT (__stdcall *SetOptions)(IFileDialog *This, FILEOPENDIALOGOPTIONS fos); 266 HRESULT (__stdcall *GetOptions)(IFileDialog *This, FILEOPENDIALOGOPTIONS *pfos); 267 HRESULT (__stdcall *SetDefaultFolder)(IFileDialog *This, IShellItem *psi); 268 HRESULT (__stdcall *SetFolder)(IFileDialog *This, IShellItem *psi); 269 HRESULT (__stdcall *GetFolder)(IFileDialog *This, IShellItem **ppsi); 270 HRESULT (__stdcall *GetCurrentSelection)(IFileDialog *This, IShellItem **ppsi); 271 HRESULT (__stdcall *SetFileName)(IFileDialog *This, LPCWSTR pszName); 272 HRESULT (__stdcall *GetFileName)(IFileDialog *This, LPWSTR *pszName); 273 HRESULT (__stdcall *SetTitle)(IFileDialog *This, LPCWSTR pszTitle); 274 HRESULT (__stdcall *SetOkButtonLabel)(IFileDialog *This, LPCWSTR pszText); 275 HRESULT (__stdcall *SetFileNameLabel)(IFileDialog *This, LPCWSTR pszLabel); 276 HRESULT (__stdcall *GetResult)(IFileDialog *This, IShellItem **ppsi); 277 HRESULT (__stdcall *AddPlace)(IFileDialog *This, IShellItem *psi, FDAP fdap); 278 HRESULT (__stdcall *SetDefaultExtension)(IFileDialog *This, LPCWSTR pszDefaultExtension); 279 HRESULT (__stdcall *Close)(IFileDialog *This, HRESULT hr); 280 HRESULT (__stdcall *SetClientGuid)(IFileDialog *This, REFGUID guid); 281 HRESULT (__stdcall *ClearClientData)(IFileDialog *This); 282 HRESULT (__stdcall *SetFilter)(IFileDialog *This, IShellItemFilter *pFilter); 283} IFileDialogVtbl; 284 285struct IFileDialog 286{ 287 IFileDialogVtbl *lpVtbl; 288}; 289 290#endif // #ifndef __IFileDialog_INTERFACE_DEFINED__ 291 292#ifndef __IFileOpenDialog_INTERFACE_DEFINED__ 293typedef struct IFileOpenDialogVtbl 294{ 295 HRESULT (__stdcall *QueryInterface)(IFileOpenDialog *This, REFIID riid, void **ppvObject); 296 ULONG (__stdcall *AddRef)(IFileOpenDialog *This); 297 ULONG (__stdcall *Release)(IFileOpenDialog *This); 298 HRESULT (__stdcall *Show)(IFileOpenDialog *This, HWND hwndOwner); 299 HRESULT (__stdcall *SetFileTypes)(IFileOpenDialog *This, UINT cFileTypes, const COMDLG_FILTERSPEC *rgFilterSpec); 300 HRESULT (__stdcall *SetFileTypeIndex)(IFileOpenDialog *This, UINT iFileType); 301 HRESULT (__stdcall *GetFileTypeIndex)(IFileOpenDialog *This, UINT *piFileType); 302 HRESULT (__stdcall *Advise)(IFileOpenDialog *This, IFileDialogEvents *pfde, DWORD *pdwCookie); 303 HRESULT (__stdcall *Unadvise)(IFileOpenDialog *This, DWORD dwCookie); 304 HRESULT (__stdcall *SetOptions)(IFileOpenDialog *This, FILEOPENDIALOGOPTIONS fos); 305 HRESULT (__stdcall *GetOptions)(IFileOpenDialog *This, FILEOPENDIALOGOPTIONS *pfos); 306 HRESULT (__stdcall *SetDefaultFolder)(IFileOpenDialog *This, IShellItem *psi); 307 HRESULT (__stdcall *SetFolder)(IFileOpenDialog *This, IShellItem *psi); 308 HRESULT (__stdcall *GetFolder)(IFileOpenDialog *This, IShellItem **ppsi); 309 HRESULT (__stdcall *GetCurrentSelection)(IFileOpenDialog *This, IShellItem **ppsi); 310 HRESULT (__stdcall *SetFileName)(IFileOpenDialog *This, LPCWSTR pszName); 311 HRESULT (__stdcall *GetFileName)(IFileOpenDialog *This, LPWSTR *pszName); 312 HRESULT (__stdcall *SetTitle)(IFileOpenDialog *This, LPCWSTR pszTitle); 313 HRESULT (__stdcall *SetOkButtonLabel)(IFileOpenDialog *This, LPCWSTR pszText); 314 HRESULT (__stdcall *SetFileNameLabel)(IFileOpenDialog *This, LPCWSTR pszLabel); 315 HRESULT (__stdcall *GetResult)(IFileOpenDialog *This, IShellItem **ppsi); 316 HRESULT (__stdcall *AddPlace)(IFileOpenDialog *This, IShellItem *psi, FDAP fdap); 317 HRESULT (__stdcall *SetDefaultExtension)(IFileOpenDialog *This, LPCWSTR pszDefaultExtension); 318 HRESULT (__stdcall *Close)(IFileOpenDialog *This, HRESULT hr); 319 HRESULT (__stdcall *SetClientGuid)(IFileOpenDialog *This, REFGUID guid); 320 HRESULT (__stdcall *ClearClientData)(IFileOpenDialog *This); 321 HRESULT (__stdcall *SetFilter)(IFileOpenDialog *This, IShellItemFilter *pFilter); 322 HRESULT (__stdcall *GetResults)(IFileOpenDialog *This, IShellItemArray **ppenum); 323 HRESULT (__stdcall *GetSelectedItems)(IFileOpenDialog *This, IShellItemArray **ppsai); 324} IFileOpenDialogVtbl; 325 326struct IFileOpenDialog 327{ 328 IFileOpenDialogVtbl *lpVtbl; 329}; 330 331#endif // #ifndef __IFileOpenDialog_INTERFACE_DEFINED__ 332 333#ifndef __IFileDialog2_INTERFACE_DEFINED__ 334typedef struct IFileDialog2Vtbl 335{ 336 HRESULT (__stdcall *QueryInterface)(IFileDialog2 *This, REFIID riid, void **ppvObject); 337 ULONG (__stdcall *AddRef)(IFileDialog2 *This); 338 ULONG (__stdcall *Release)(IFileDialog2 *This); 339 HRESULT (__stdcall *Show)(IFileDialog2 *This, HWND hwndOwner); 340 HRESULT (__stdcall *SetFileTypes)(IFileDialog2 *This, UINT cFileTypes, const COMDLG_FILTERSPEC *rgFilterSpec); 341 HRESULT (__stdcall *SetFileTypeIndex)(IFileDialog2 *This, UINT iFileType); 342 HRESULT (__stdcall *GetFileTypeIndex)(IFileDialog2 *This, UINT *piFileType); 343 HRESULT (__stdcall *Advise)(IFileDialog2 *This, IFileDialogEvents *pfde, DWORD *pdwCookie); 344 HRESULT (__stdcall *Unadvise)(IFileDialog2 *This, DWORD dwCookie); 345 HRESULT (__stdcall *SetOptions)(IFileDialog2 *This, FILEOPENDIALOGOPTIONS fos); 346 HRESULT (__stdcall *GetOptions)(IFileDialog2 *This, FILEOPENDIALOGOPTIONS *pfos); 347 HRESULT (__stdcall *SetDefaultFolder)(IFileDialog2 *This, IShellItem *psi); 348 HRESULT (__stdcall *SetFolder)(IFileDialog2 *This, IShellItem *psi); 349 HRESULT (__stdcall *GetFolder)(IFileDialog2 *This, IShellItem **ppsi); 350 HRESULT (__stdcall *GetCurrentSelection)(IFileDialog2 *This, IShellItem **ppsi); 351 HRESULT (__stdcall *SetFileName)(IFileDialog2 *This, LPCWSTR pszName); 352 HRESULT (__stdcall *GetFileName)(IFileDialog2 *This, LPWSTR *pszName); 353 HRESULT (__stdcall *SetTitle)(IFileDialog2 *This, LPCWSTR pszTitle); 354 HRESULT (__stdcall *SetOkButtonLabel)(IFileDialog2 *This, LPCWSTR pszText); 355 HRESULT (__stdcall *SetFileNameLabel)(IFileDialog2 *This, LPCWSTR pszLabel); 356 HRESULT (__stdcall *GetResult)(IFileDialog2 *This, IShellItem **ppsi); 357 HRESULT (__stdcall *AddPlace)(IFileDialog2 *This, IShellItem *psi, FDAP fdap); 358 HRESULT (__stdcall *SetDefaultExtension)(IFileDialog2 *This, LPCWSTR pszDefaultExtension); 359 HRESULT (__stdcall *Close)(IFileDialog2 *This, HRESULT hr); 360 HRESULT (__stdcall *SetClientGuid)(IFileDialog2 *This, REFGUID guid); 361 HRESULT (__stdcall *ClearClientData)(IFileDialog2 *This); 362 HRESULT (__stdcall *SetFilter)(IFileDialog2 *This, IShellItemFilter *pFilter); 363 HRESULT (__stdcall *SetCancelButtonLabel)(IFileDialog2 *This, LPCWSTR pszLabel); 364 HRESULT (__stdcall *SetNavigationRoot)(IFileDialog2 *This, IShellItem *psi); 365} IFileDialog2Vtbl; 366 367struct IFileDialog2 368{ 369 IFileDialog2Vtbl *lpVtbl; 370}; 371 372#endif // #ifndef __IFileDialog2_INTERFACE_DEFINED__ 373 374/* *INDENT-OFF* */ // clang-format off 375static const CLSID SDL_CLSID_FileOpenDialog = { 0xdc1c5a9c, 0xe88a, 0x4dde, { 0xa5, 0xa1, 0x60, 0xf8, 0x2a, 0x20, 0xae, 0xf7 } }; 376static const CLSID SDL_CLSID_FileSaveDialog = { 0xc0b4e2f3, 0xba21, 0x4773, { 0x8d, 0xba, 0x33, 0x5e, 0xc9, 0x46, 0xeb, 0x8b } }; 377 378static const IID SDL_IID_IFileDialog = { 0x42f85136, 0xdb7e, 0x439c, { 0x85, 0xf1, 0xe4, 0x07, 0x5d, 0x13, 0x5f, 0xc8 } }; 379static const IID SDL_IID_IFileDialog2 = { 0x61744fc7, 0x85b5, 0x4791, { 0xa9, 0xb0, 0x27, 0x22, 0x76, 0x30, 0x9b, 0x13 } }; 380static const IID SDL_IID_IFileOpenDialog = { 0xd57c7288, 0xd4ad, 0x4768, { 0xbe, 0x02, 0x9d, 0x96, 0x95, 0x32, 0xd9, 0x60 } }; 381/* *INDENT-ON* */ // clang-format on 382 383// If this number is too small, selecting too many files will give an error 384#define SELECTLIST_SIZE 65536 385 386typedef struct 387{ 388 bool is_save; 389 wchar_t *filters_str; 390 int nfilters; 391 char *default_file; 392 SDL_Window *parent; 393 DWORD flags; 394 bool allow_many; 395 SDL_DialogFileCallback callback; 396 void *userdata; 397 char *title; 398 char *accept; 399 char *cancel; 400} winArgs; 401 402typedef struct 403{ 404 SDL_Window *parent; 405 bool allow_many; 406 SDL_DialogFileCallback callback; 407 char *default_folder; 408 void *userdata; 409 char *title; 410 char *accept; 411 char *cancel; 412} winFArgs; 413 414void freeWinArgs(winArgs *args) 415{ 416 SDL_free(args->default_file); 417 SDL_free(args->filters_str); 418 SDL_free(args->title); 419 SDL_free(args->accept); 420 SDL_free(args->cancel); 421 422 SDL_free(args); 423} 424 425void freeWinFArgs(winFArgs *args) 426{ 427 SDL_free(args->default_folder); 428 SDL_free(args->title); 429 SDL_free(args->accept); 430 SDL_free(args->cancel); 431 432 SDL_free(args); 433} 434 435/** Converts dialog.nFilterIndex to SDL-compatible value */ 436int getFilterIndex(int as_reported_by_windows) 437{ 438 return as_reported_by_windows - 1; 439} 440 441char *clear_filt_names(const char *filt) 442{ 443 char *cleared = SDL_strdup(filt); 444 445 for (char *c = cleared; *c; c++) { 446 /* 0x01 bytes are used as temporary replacement for the various 0x00 447 bytes required by Win32 (one null byte between each filter, two at 448 the end of the filters). Filter out these bytes from the filter names 449 to avoid early-ending the filters if someone puts two consecutive 450 0x01 bytes in their filter names. */ 451 if (*c == '\x01') { 452 *c = ' '; 453 } 454 } 455 456 return cleared; 457} 458 459// This function returns NOT success or error, but rather whether the callback 460// was invoked or not (and if it was, no fallback should be attempted to prevent 461// calling the callback twice). See https://github.com/libsdl-org/SDL/issues/15194 462bool windows_ShowModernFileFolderDialog(SDL_FileDialogType dialog_type, const char *default_file, SDL_Window *parent, bool allow_many, SDL_DialogFileCallback callback, void *userdata, const char *title, const char *accept, const char *cancel, wchar_t *filter_wchar, int nfilters) 463{ 464 bool is_save = dialog_type == SDL_FILEDIALOG_SAVEFILE; 465 bool is_folder = dialog_type == SDL_FILEDIALOG_OPENFOLDER; 466 467 if (is_save) { 468 // Just in case; the code relies on that 469 allow_many = false; 470 } 471 472 HMODULE shell32_handle = NULL; 473 474 typedef HRESULT(WINAPI *pfnSHCreateItemFromParsingName)(PCWSTR, IBindCtx *, REFIID, void **); 475 pfnSHCreateItemFromParsingName pSHCreateItemFromParsingName = NULL; 476 477 IFileDialog *pFileDialog = NULL; 478 IFileOpenDialog *pFileOpenDialog = NULL; 479 IFileDialog2 *pFileDialog2 = NULL; 480 IShellItemArray *pItemArray = NULL; 481 IShellItem *pItem = NULL; 482 IShellItem *pFolderItem = NULL; 483 LPWSTR filePath = NULL; 484 COMDLG_FILTERSPEC *filter_data = NULL; 485 char **files = NULL; 486 wchar_t *title_w = NULL; 487 wchar_t *accept_w = NULL; 488 wchar_t *cancel_w = NULL; 489 FILEOPENDIALOGOPTIONS pfos; 490 491 wchar_t *default_file_w = NULL; 492 wchar_t *default_folder_w = NULL; 493 494 bool callback_called = false; 495 bool call_callback_on_error = false; 496 bool co_init = false; 497 498 // We can assume shell32 is already loaded here. 499 shell32_handle = GetModuleHandle(TEXT("shell32.dll")); 500 if (!shell32_handle) { 501 goto quit; 502 } 503 504 pSHCreateItemFromParsingName = (pfnSHCreateItemFromParsingName)GetProcAddress(shell32_handle, "SHCreateItemFromParsingName"); 505 if (!pSHCreateItemFromParsingName) { 506 goto quit; 507 } 508 509 if (filter_wchar && nfilters > 0) { 510 wchar_t *filter_ptr = filter_wchar; 511 filter_data = SDL_calloc(sizeof(COMDLG_FILTERSPEC), nfilters); 512 if (!filter_data) { 513 goto quit; 514 } 515 516 for (int i = 0; i < nfilters; i++) { 517 filter_data[i].pszName = filter_ptr; 518 filter_ptr += SDL_wcslen(filter_ptr) + 1; 519 filter_data[i].pszSpec = filter_ptr; 520 filter_ptr += SDL_wcslen(filter_ptr) + 1; 521 } 522 523 // assert(*filter_ptr == L'\0'); 524 } 525 526 if (title && (title_w = WIN_UTF8ToStringW(title)) == NULL) { 527 goto quit; 528 } 529 530 if (accept && (accept_w = WIN_UTF8ToStringW(accept)) == NULL) { 531 goto quit; 532 } 533 534 if (cancel && (cancel_w = WIN_UTF8ToStringW(cancel)) == NULL) { 535 goto quit; 536 } 537 538 if (default_file) { 539 default_folder_w = WIN_UTF8ToStringW(default_file); 540 541 if (!default_folder_w) { 542 goto quit; 543 } 544 545 for (wchar_t *chrptr = default_folder_w; *chrptr; chrptr++) { 546 if (*chrptr == L'/' || *chrptr == L'\\') { 547 default_file_w = chrptr; 548 } 549 } 550 551 if (!default_file_w) { 552 default_file_w = default_folder_w; 553 default_folder_w = NULL; 554 } else { 555 *default_file_w = L'\0'; 556 default_file_w++; 557 558 if (SDL_wcslen(default_file_w) == 0) { 559 default_file_w = NULL; 560 } 561 } 562 } 563 564#define CHECK(op) if (!SUCCEEDED(op)) { goto quit; } 565 566 CHECK(WIN_CoInitialize()); 567 568 co_init = true; 569 570 CHECK(CoCreateInstance(is_save ? &SDL_CLSID_FileSaveDialog : &SDL_CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, &SDL_IID_IFileDialog, (void**)&pFileDialog)); 571 CHECK(pFileDialog->lpVtbl->QueryInterface(pFileDialog, &SDL_IID_IFileDialog2, (void**)&pFileDialog2)); 572 573 if (allow_many) { 574 CHECK(pFileDialog->lpVtbl->QueryInterface(pFileDialog, &SDL_IID_IFileOpenDialog, (void**)&pFileOpenDialog)); 575 } 576 577 CHECK(pFileDialog2->lpVtbl->GetOptions(pFileDialog2, &pfos)); 578 579 pfos |= FOS_NOCHANGEDIR; 580 if (allow_many) pfos |= FOS_ALLOWMULTISELECT; 581 if (is_save) pfos |= FOS_OVERWRITEPROMPT; 582 if (is_folder) pfos |= FOS_PICKFOLDERS; 583 584 CHECK(pFileDialog2->lpVtbl->SetOptions(pFileDialog2, pfos)); 585 586 if (cancel_w) { 587 CHECK(pFileDialog2->lpVtbl->SetCancelButtonLabel(pFileDialog2, cancel_w)); 588 } 589 590 if (accept_w) { 591 CHECK(pFileDialog->lpVtbl->SetOkButtonLabel(pFileDialog, accept_w)); 592 } 593 594 if (title_w) { 595 CHECK(pFileDialog->lpVtbl->SetTitle(pFileDialog, title_w)); 596 } 597 598 if (filter_data) { 599 CHECK(pFileDialog->lpVtbl->SetFileTypes(pFileDialog, nfilters, filter_data)); 600 } 601 602 if (default_folder_w) { 603 CHECK(pSHCreateItemFromParsingName(default_folder_w, NULL, &IID_IShellItem, (void**)&pFolderItem)); 604 CHECK(pFileDialog->lpVtbl->SetFolder(pFileDialog, pFolderItem)); 605 } 606 607 if (default_file_w) { 608 CHECK(pFileDialog->lpVtbl->SetFileName(pFileDialog, default_file_w)); 609 } 610 611 // Right after this, a dialog is shown. No fallback should be attempted on 612 // error to prevent showing two dialogs to the user. 613 call_callback_on_error = true; 614 615 if (parent) { 616 HWND window = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(parent), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); 617 618 HRESULT hr = pFileDialog->lpVtbl->Show(pFileDialog, window); 619 620 if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) { 621 const char * const results[] = { NULL }; 622 UINT selected_filter; 623 624 // This is a one-based index, not zero-based. Doc link in similar comment below 625 CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); 626 callback(userdata, results, selected_filter - 1); 627 callback_called = true; 628 goto quit; 629 } else if (!SUCCEEDED(hr)) { 630 goto quit; 631 } 632 } else { 633 HRESULT hr = pFileDialog->lpVtbl->Show(pFileDialog, NULL); 634 635 if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) { 636 const char * const results[] = { NULL }; 637 UINT selected_filter; 638 639 // This is a one-based index, not zero-based. Doc link in similar comment below 640 CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); 641 callback(userdata, results, selected_filter - 1); 642 callback_called = true; 643 goto quit; 644 } else if (!SUCCEEDED(hr)) { 645 goto quit; 646 } 647 } 648 649 if (allow_many) { 650 DWORD nResults; 651 UINT selected_filter; 652 653 CHECK(pFileOpenDialog->lpVtbl->GetResults(pFileOpenDialog, &pItemArray)); 654 CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); 655 CHECK(pItemArray->lpVtbl->GetCount(pItemArray, &nResults)); 656 657 files = SDL_calloc(nResults + 1, sizeof(char*)); 658 if (!files) { 659 goto quit; 660 } 661 char** files_ptr = files; 662 663 for (DWORD i = 0; i < nResults; i++) { 664 CHECK(pItemArray->lpVtbl->GetItemAt(pItemArray, i, &pItem)); 665 CHECK(pItem->lpVtbl->GetDisplayName(pItem, SIGDN_FILESYSPATH, &filePath)); 666 667 *(files_ptr++) = WIN_StringToUTF8(filePath); 668 669 CoTaskMemFree(filePath); 670 filePath = NULL; 671 pItem->lpVtbl->Release(pItem); 672 pItem = NULL; 673 } 674 675 callback(userdata, (const char * const *) files, selected_filter - 1); 676 callback_called = true; 677 } else { 678 // This is a one-based index, not zero-based. 679 // https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-getfiletypeindex#parameters 680 UINT selected_filter; 681 682 CHECK(pFileDialog->lpVtbl->GetResult(pFileDialog, &pItem)); 683 CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); 684 CHECK(pItem->lpVtbl->GetDisplayName(pItem, SIGDN_FILESYSPATH, &filePath)); 685 686 char *file = WIN_StringToUTF8(filePath); 687 if (!file) { 688 goto quit; 689 } 690 const char * const results[] = { file, NULL }; 691 callback(userdata, results, selected_filter - 1); 692 callback_called = true; 693 SDL_free(file); 694 } 695 696#undef CHECK 697 698quit: 699 if (!callback_called && call_callback_on_error) { 700 WIN_SetError("dialog"); 701 callback(userdata, NULL, -1); 702 callback_called = true; 703 } 704 705 if (co_init) { 706 WIN_CoUninitialize(); 707 } 708 709 if (pFileOpenDialog) { 710 pFileOpenDialog->lpVtbl->Release(pFileOpenDialog); 711 } 712 713 if (pFileDialog2) { 714 pFileDialog2->lpVtbl->Release(pFileDialog2); 715 } 716 717 if (pFileDialog) { 718 pFileDialog->lpVtbl->Release(pFileDialog); 719 } 720 721 if (pItem) { 722 pItem->lpVtbl->Release(pItem); 723 } 724 725 if (pFolderItem) { 726 pFolderItem->lpVtbl->Release(pFolderItem); 727 } 728 729 if (pItemArray) { 730 pItemArray->lpVtbl->Release(pItemArray); 731 } 732 733 if (filePath) { 734 CoTaskMemFree(filePath); 735 } 736 737 // If both default_file_w and default_folder_w are non-NULL, then 738 // default_file_w is a pointer into default_folder_w. 739 if (default_folder_w) { 740 SDL_free(default_folder_w); 741 } else { 742 SDL_free(default_file_w); 743 } 744 745 SDL_free(title_w); 746 747 SDL_free(accept_w); 748 749 SDL_free(cancel_w); 750 751 SDL_free(filter_data); 752 753 if (files) { 754 for (char** files_ptr = files; *files_ptr; files_ptr++) { 755 SDL_free(*files_ptr); 756 } 757 SDL_free(files); 758 } 759 760 return callback_called; 761} 762 763// TODO: The new version of file dialogs 764void windows_ShowFileDialog(void *ptr) 765{ 766 767 winArgs *args = (winArgs *) ptr; 768 bool is_save = args->is_save; 769 const char *default_file = args->default_file; 770 SDL_Window *parent = args->parent; 771 DWORD flags = args->flags; 772 bool allow_many = args->allow_many; 773 SDL_DialogFileCallback callback = args->callback; 774 void *userdata = args->userdata; 775 const char *title = args->title; 776 const char *accept = args->accept; 777 const char *cancel = args->cancel; 778 wchar_t *filter_wchar = args->filters_str; 779 int nfilters = args->nfilters; 780 781 if (windows_ShowModernFileFolderDialog(is_save ? SDL_FILEDIALOG_SAVEFILE : SDL_FILEDIALOG_OPENFILE, default_file, parent, allow_many, callback, userdata, title, accept, cancel, filter_wchar, nfilters)) { 782 return; 783 } 784 785 /* GetOpenFileName and GetSaveFileName have the same signature 786 (yes, LPOPENFILENAMEW even for the save dialog) */ 787 typedef BOOL (WINAPI *pfnGetAnyFileNameW)(LPOPENFILENAMEW); 788 typedef DWORD (WINAPI *pfnCommDlgExtendedError)(void); 789 HMODULE lib = LoadLibraryW(L"Comdlg32.dll"); 790 pfnGetAnyFileNameW pGetAnyFileName = NULL; 791 pfnCommDlgExtendedError pCommDlgExtendedError = NULL; 792 793 if (lib) { 794 pGetAnyFileName = (pfnGetAnyFileNameW) GetProcAddress(lib, is_save ? "GetSaveFileNameW" : "GetOpenFileNameW"); 795 pCommDlgExtendedError = (pfnCommDlgExtendedError) GetProcAddress(lib, "CommDlgExtendedError"); 796 } else { 797 SDL_SetError("Couldn't load Comdlg32.dll"); 798 callback(userdata, NULL, -1); 799 return; 800 } 801 802 if (!pGetAnyFileName) { 803 SDL_SetError("Couldn't load GetOpenFileName/GetSaveFileName from library"); 804 callback(userdata, NULL, -1); 805 return; 806 } 807 808 if (!pCommDlgExtendedError) { 809 SDL_SetError("Couldn't load CommDlgExtendedError from library"); 810 callback(userdata, NULL, -1); 811 return; 812 } 813 814 HWND window = NULL; 815 816 if (parent) { 817 window = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(parent), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); 818 } 819 820 wchar_t *filebuffer; // lpstrFile 821 wchar_t initfolder[MAX_PATH] = L""; // lpstrInitialDir 822 823 /* If SELECTLIST_SIZE is too large, putting filebuffer on the stack might 824 cause an overflow */ 825 filebuffer = (wchar_t *) SDL_malloc(SELECTLIST_SIZE * sizeof(wchar_t)); 826 827 // Necessary for the return code below 828 SDL_memset(filebuffer, 0, SELECTLIST_SIZE * sizeof(wchar_t)); 829 830 if (default_file) { 831 /* On Windows 10, 11 and possibly others, lpstrFile can be initialized 832 with a path and the dialog will start at that location, but *only if 833 the path contains a filename*. If it ends with a folder (directory 834 separator), it fails with 0x3002 (12290) FNERR_INVALIDFILENAME. For 835 that specific case, lpstrInitialDir must be used instead, but just 836 for that case, because lpstrInitialDir doesn't support file names. 837 838 On top of that, lpstrInitialDir hides a special algorithm that 839 decides which folder to actually use as starting point, which may or 840 may not be the one provided, or some other unrelated folder. Also, 841 the algorithm changes between platforms. Assuming the documentation 842 is correct, the algorithm is there under 'lpstrInitialDir': 843 844 https://learn.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-openfilenamew 845 846 Finally, lpstrFile does not support forward slashes. lpstrInitialDir 847 does, though. */ 848 849 char last_c = default_file[SDL_strlen(default_file) - 1]; 850 851 if (last_c == '\\' || last_c == '/') { 852 MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, default_file, -1, initfolder, MAX_PATH); 853 } else { 854 MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, default_file, -1, filebuffer, MAX_PATH); 855 856 for (int i = 0; i < SELECTLIST_SIZE; i++) { 857 if (filebuffer[i] == L'/') { 858 filebuffer[i] = L'\\'; 859 } 860 } 861 } 862 } 863 864 wchar_t *title_w = NULL; 865 866 if (title) { 867 title_w = WIN_UTF8ToStringW(title); 868 if (!title_w) { 869 SDL_free(filebuffer); 870 callback(userdata, NULL, -1); 871 return; 872 } 873 } 874 875 OPENFILENAMEW dialog; 876 dialog.lStructSize = sizeof(OPENFILENAME); 877 dialog.hwndOwner = window; 878 dialog.hInstance = 0; 879 dialog.lpstrFilter = filter_wchar; 880 dialog.lpstrCustomFilter = NULL; 881 dialog.nMaxCustFilter = 0; 882 dialog.nFilterIndex = 0; 883 dialog.lpstrFile = filebuffer; 884 dialog.nMaxFile = SELECTLIST_SIZE; 885 dialog.lpstrFileTitle = NULL; 886 dialog.lpstrInitialDir = *initfolder ? initfolder : NULL; 887 dialog.lpstrTitle = title_w; 888 dialog.Flags = flags | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_NOCHANGEDIR; 889 dialog.nFileOffset = 0; 890 dialog.nFileExtension = 0; 891 dialog.lpstrDefExt = NULL; 892 dialog.lCustData = 0; 893 dialog.lpfnHook = NULL; 894 dialog.lpTemplateName = NULL; 895 // Skipped many mac-exclusive and reserved members 896 dialog.FlagsEx = 0; 897 898 BOOL result = pGetAnyFileName(&dialog); 899 900 SDL_free(title_w); 901 902 if (result) { 903 if (!(flags & OFN_ALLOWMULTISELECT)) { 904 // File is a C string stored in dialog.lpstrFile 905 char *chosen_file = WIN_StringToUTF8W(dialog.lpstrFile); 906 const char *opts[2] = { chosen_file, NULL }; 907 callback(userdata, opts, getFilterIndex(dialog.nFilterIndex)); 908 SDL_free(chosen_file); 909 } else { 910 /* File is either a C string if the user chose a single file, else 911 it's a series of strings formatted like: 912 913 "C:\\path\\to\\folder\0filename1.ext\0filename2.ext\0\0" 914 915 The code below will only stop on a double NULL in all cases, so 916 it is important that the rest of the buffer has been zeroed. */ 917 char chosen_folder[MAX_PATH]; 918 char chosen_file[MAX_PATH]; 919 wchar_t *file_ptr = dialog.lpstrFile; 920 size_t nfiles = 0; 921 size_t chosen_folder_size; 922 char **chosen_files_list = (char **) SDL_malloc(sizeof(char *) * (nfiles + 1)); 923 924 if (!chosen_files_list) { 925 callback(userdata, NULL, -1); 926 SDL_free(filebuffer); 927 return; 928 } 929 930 chosen_files_list[nfiles] = NULL; 931 932 if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_folder, MAX_PATH, NULL, NULL) == 0) { 933 SDL_SetError("Path too long or invalid character in path"); 934 SDL_free(chosen_files_list); 935 callback(userdata, NULL, -1); 936 SDL_free(filebuffer); 937 return; 938 } 939 940 chosen_folder_size = SDL_strlen(chosen_folder); 941 SDL_strlcpy(chosen_file, chosen_folder, MAX_PATH); 942 chosen_file[chosen_folder_size] = '\\'; 943 944 file_ptr += SDL_wcslen(file_ptr) + 1; 945 946 while (*file_ptr) { 947 nfiles++; 948 char **new_cfl = (char **) SDL_realloc(chosen_files_list, sizeof(char *) * (nfiles + 1)); 949 950 if (!new_cfl) { 951 for (size_t i = 0; i < nfiles - 1; i++) { 952 SDL_free(chosen_files_list[i]); 953 } 954 955 SDL_free(chosen_files_list); 956 callback(userdata, NULL, -1); 957 SDL_free(filebuffer); 958 return; 959 } 960 961 chosen_files_list = new_cfl; 962 chosen_files_list[nfiles] = NULL; 963 964 int diff = ((int) chosen_folder_size) + 1; 965 966 if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_file + diff, MAX_PATH - diff, NULL, NULL) == 0) { 967 SDL_SetError("Path too long or invalid character in path"); 968 969 for (size_t i = 0; i < nfiles - 1; i++) { 970 SDL_free(chosen_files_list[i]); 971 } 972 973 SDL_free(chosen_files_list); 974 callback(userdata, NULL, -1); 975 SDL_free(filebuffer); 976 return; 977 } 978 979 file_ptr += SDL_wcslen(file_ptr) + 1; 980 981 chosen_files_list[nfiles - 1] = SDL_strdup(chosen_file); 982 983 if (!chosen_files_list[nfiles - 1]) { 984 for (size_t i = 0; i < nfiles - 1; i++) { 985 SDL_free(chosen_files_list[i]); 986 } 987 988 SDL_free(chosen_files_list); 989 callback(userdata, NULL, -1); 990 SDL_free(filebuffer); 991 return; 992 } 993 } 994 995 // If the user chose only one file, it's all just one string 996 if (nfiles == 0) { 997 nfiles++; 998 char **new_cfl = (char **) SDL_realloc(chosen_files_list, sizeof(char *) * (nfiles + 1)); 999 1000 if (!new_cfl) { 1001 SDL_free(chosen_files_list); 1002 callback(userdata, NULL, -1); 1003 SDL_free(filebuffer); 1004 return; 1005 } 1006 1007 chosen_files_list = new_cfl; 1008 chosen_files_list[nfiles] = NULL; 1009 chosen_files_list[nfiles - 1] = SDL_strdup(chosen_folder); 1010 1011 if (!chosen_files_list[nfiles - 1]) { 1012 SDL_free(chosen_files_list); 1013 callback(userdata, NULL, -1); 1014 SDL_free(filebuffer); 1015 return; 1016 } 1017 } 1018 1019 callback(userdata, (const char * const *) chosen_files_list, getFilterIndex(dialog.nFilterIndex)); 1020 1021 for (size_t i = 0; i < nfiles; i++) { 1022 SDL_free(chosen_files_list[i]); 1023 } 1024 1025 SDL_free(chosen_files_list); 1026 } 1027 } else { 1028 DWORD error = pCommDlgExtendedError(); 1029 // Error code 0 means the user clicked the cancel button. 1030 if (error == 0) { 1031 /* Unlike SDL's handling of errors, Windows does reset the error 1032 code to 0 after calling GetOpenFileName if another Windows 1033 function before set a different error code, so it's safe to 1034 check for success. */ 1035 const char *opts[1] = { NULL }; 1036 callback(userdata, opts, getFilterIndex(dialog.nFilterIndex)); 1037 } else { 1038 SDL_SetError("Windows error, CommDlgExtendedError: %ld", pCommDlgExtendedError()); 1039 callback(userdata, NULL, -1); 1040 } 1041 } 1042 1043 SDL_free(filebuffer); 1044} 1045 1046int windows_file_dialog_thread(void *ptr) 1047{ 1048 windows_ShowFileDialog(ptr); 1049 freeWinArgs(ptr); 1050 return 0; 1051} 1052 1053int CALLBACK browse_callback_proc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) 1054{ 1055 switch (uMsg) { 1056 case BFFM_INITIALIZED: 1057 if (lpData) { 1058 SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData); 1059 } 1060 break; 1061 case BFFM_SELCHANGED: 1062 break; 1063 case BFFM_VALIDATEFAILED: 1064 break; 1065 default: 1066 break; 1067 } 1068 return 0; 1069} 1070 1071void windows_ShowFolderDialog(void *ptr) 1072{ 1073 winFArgs *args = (winFArgs *) ptr; 1074 SDL_Window *window = args->parent; 1075 SDL_DialogFileCallback callback = args->callback; 1076 void *userdata = args->userdata; 1077 HWND parent = NULL; 1078 int allow_many = args->allow_many; 1079 char *default_folder = args->default_folder; 1080 const char *title = args->title; 1081 const char *accept = args->accept; 1082 const char *cancel = args->cancel; 1083 1084 if (windows_ShowModernFileFolderDialog(SDL_FILEDIALOG_OPENFOLDER, default_folder, window, allow_many, callback, userdata, title, accept, cancel, NULL, 0)) { 1085 return; 1086 } 1087 1088 if (window) { 1089 parent = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); 1090 } 1091 1092 wchar_t *title_w = NULL; 1093 1094 if (title) { 1095 title_w = WIN_UTF8ToStringW(title); 1096 if (!title_w) { 1097 callback(userdata, NULL, -1); 1098 return; 1099 } 1100 } 1101 1102 wchar_t buffer[MAX_PATH]; 1103 1104 BROWSEINFOW dialog; 1105 dialog.hwndOwner = parent; 1106 dialog.pidlRoot = NULL; 1107 dialog.pszDisplayName = buffer; 1108 dialog.lpszTitle = title_w; 1109 dialog.ulFlags = BIF_USENEWUI; 1110 dialog.lpfn = browse_callback_proc; 1111 dialog.lParam = (LPARAM)args->default_folder; 1112 dialog.iImage = 0; 1113 1114 LPITEMIDLIST lpItem = SHBrowseForFolderW(&dialog); 1115 1116 SDL_free(title_w); 1117 1118 if (lpItem != NULL) { 1119 SHGetPathFromIDListW(lpItem, buffer); 1120 char *chosen_file = WIN_StringToUTF8W(buffer); 1121 const char *files[2] = { chosen_file, NULL }; 1122 callback(userdata, (const char * const *) files, -1); 1123 SDL_free(chosen_file); 1124 } else { 1125 const char *files[1] = { NULL }; 1126 callback(userdata, (const char * const *) files, -1); 1127 } 1128} 1129 1130int windows_folder_dialog_thread(void *ptr) 1131{ 1132 windows_ShowFolderDialog(ptr); 1133 freeWinFArgs((winFArgs *)ptr); 1134 return 0; 1135} 1136 1137wchar_t *win_get_filters(const SDL_DialogFileFilter *filters, int nfilters) 1138{ 1139 wchar_t *filter_wchar = NULL; 1140 1141 if (filters) { 1142 // '\x01' is used in place of a null byte 1143 // suffix needs two null bytes in case the filter list is empty 1144 char *filterlist = convert_filters(filters, nfilters, clear_filt_names, 1145 "", "", "\x01\x01", "", "\x01", 1146 "\x01", "*.", ";*.", "", false); 1147 1148 if (!filterlist) { 1149 return NULL; 1150 } 1151 1152 int filter_len = (int)SDL_strlen(filterlist); 1153 1154 for (char *c = filterlist; *c; c++) { 1155 if (*c == '\x01') { 1156 *c = '\0'; 1157 } 1158 } 1159 1160 int filter_wlen = MultiByteToWideChar(CP_UTF8, 0, filterlist, filter_len, NULL, 0); 1161 filter_wchar = (wchar_t *)SDL_malloc(filter_wlen * sizeof(wchar_t)); 1162 if (!filter_wchar) { 1163 SDL_free(filterlist); 1164 return NULL; 1165 } 1166 1167 MultiByteToWideChar(CP_UTF8, 0, filterlist, filter_len, filter_wchar, filter_wlen); 1168 1169 SDL_free(filterlist); 1170 } 1171 1172 return filter_wchar; 1173} 1174 1175static 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) 1176{ 1177 winArgs *args; 1178 SDL_Thread *thread; 1179 wchar_t *filters_str; 1180 1181 if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) { 1182 SDL_SetError("File dialog driver unsupported"); 1183 callback(userdata, NULL, -1); 1184 return; 1185 } 1186 1187 args = (winArgs *)SDL_malloc(sizeof(*args)); 1188 if (args == NULL) { 1189 callback(userdata, NULL, -1); 1190 return; 1191 } 1192 1193 filters_str = win_get_filters(filters, nfilters); 1194 1195 DWORD flags = 0; 1196 if (allow_many) { 1197 flags |= OFN_ALLOWMULTISELECT; 1198 } 1199 if (is_save) { 1200 flags |= OFN_OVERWRITEPROMPT; 1201 } 1202 1203 if (!filters_str && filters) { 1204 callback(userdata, NULL, -1); 1205 SDL_free(args); 1206 return; 1207 } 1208 1209 args->is_save = is_save; 1210 args->filters_str = filters_str; 1211 args->nfilters = nfilters; 1212 args->default_file = default_location ? SDL_strdup(default_location) : NULL; 1213 args->parent = window; 1214 args->flags = flags; 1215 args->allow_many = allow_many; 1216 args->callback = callback; 1217 args->userdata = userdata; 1218 args->title = title ? SDL_strdup(title) : NULL; 1219 args->accept = accept ? SDL_strdup(accept) : NULL; 1220 args->cancel = cancel ? SDL_strdup(cancel) : NULL; 1221 1222 thread = SDL_CreateThread(windows_file_dialog_thread, "SDL_Windows_ShowFileDialog", (void *) args); 1223 1224 if (thread == NULL) { 1225 callback(userdata, NULL, -1); 1226 // The thread won't have run, therefore the data won't have been freed 1227 freeWinArgs(args); 1228 return; 1229 } 1230 1231 SDL_DetachThread(thread); 1232} 1233 1234void 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) 1235{ 1236 winFArgs *args; 1237 SDL_Thread *thread; 1238 1239 if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) { 1240 SDL_SetError("File dialog driver unsupported"); 1241 callback(userdata, NULL, -1); 1242 return; 1243 } 1244 1245 args = (winFArgs *)SDL_malloc(sizeof(*args)); 1246 if (args == NULL) { 1247 callback(userdata, NULL, -1); 1248 return; 1249 } 1250 1251 args->parent = window; 1252 args->allow_many = allow_many; 1253 args->callback = callback; 1254 args->default_folder = default_location ? SDL_strdup(default_location) : NULL; 1255 args->userdata = userdata; 1256 args->title = title ? SDL_strdup(title) : NULL; 1257 args->accept = accept ? SDL_strdup(accept) : NULL; 1258 args->cancel = cancel ? SDL_strdup(cancel) : NULL; 1259 1260 thread = SDL_CreateThread(windows_folder_dialog_thread, "SDL_Windows_ShowFolderDialog", (void *) args); 1261 1262 if (thread == NULL) { 1263 callback(userdata, NULL, -1); 1264 // The thread won't have run, therefore the data won't have been freed 1265 freeWinFArgs(args); 1266 return; 1267 } 1268 1269 SDL_DetachThread(thread); 1270} 1271 1272void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) 1273{ 1274 /* The internal functions will start threads, and the properties may be freed as soon as this function returns. 1275 Save a copy of what we need before invoking the functions and starting the threads. */ 1276 SDL_Window *window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL); 1277 SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL); 1278 int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0); 1279 bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false); 1280 const char *default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL); 1281 const char *title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL); 1282 const char *accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL); 1283 const char *cancel = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_CANCEL_STRING, NULL); 1284 bool is_save = false; 1285 1286 switch (type) { 1287 case SDL_FILEDIALOG_SAVEFILE: 1288 is_save = true; 1289 SDL_FALLTHROUGH; 1290 case SDL_FILEDIALOG_OPENFILE: 1291 ShowFileDialog(callback, userdata, window, filters, nfilters, default_location, allow_many, is_save, title, accept, cancel); 1292 break; 1293 1294 case SDL_FILEDIALOG_OPENFOLDER: 1295 ShowFolderDialog(callback, userdata, window, default_location, allow_many, title, accept, cancel); 1296 break; 1297 } 1298} 1299[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.