Atlas - SDL_windowsdialog.c
Home / ext / SDL / src / dialog / windows Lines: 1 | Size: 46634 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 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 459bool 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) 460{ 461 bool is_save = dialog_type == SDL_FILEDIALOG_SAVEFILE; 462 bool is_folder = dialog_type == SDL_FILEDIALOG_OPENFOLDER; 463 464 if (is_save) { 465 // Just in case; the code relies on that 466 allow_many = false; 467 } 468 469 HMODULE shell32_handle = NULL; 470 471 typedef HRESULT(WINAPI *pfnSHCreateItemFromParsingName)(PCWSTR, IBindCtx *, REFIID, void **); 472 pfnSHCreateItemFromParsingName pSHCreateItemFromParsingName = NULL; 473 474 IFileDialog *pFileDialog = NULL; 475 IFileOpenDialog *pFileOpenDialog = NULL; 476 IFileDialog2 *pFileDialog2 = NULL; 477 IShellItemArray *pItemArray = NULL; 478 IShellItem *pItem = NULL; 479 IShellItem *pFolderItem = NULL; 480 LPWSTR filePath = NULL; 481 COMDLG_FILTERSPEC *filter_data = NULL; 482 char **files = NULL; 483 wchar_t *title_w = NULL; 484 wchar_t *accept_w = NULL; 485 wchar_t *cancel_w = NULL; 486 FILEOPENDIALOGOPTIONS pfos; 487 488 wchar_t *default_file_w = NULL; 489 wchar_t *default_folder_w = NULL; 490 491 bool success = false; 492 bool co_init = false; 493 494 // We can assume shell32 is already loaded here. 495 shell32_handle = GetModuleHandle(TEXT("shell32.dll")); 496 if (!shell32_handle) { 497 goto quit; 498 } 499 500 pSHCreateItemFromParsingName = (pfnSHCreateItemFromParsingName)GetProcAddress(shell32_handle, "SHCreateItemFromParsingName"); 501 if (!pSHCreateItemFromParsingName) { 502 goto quit; 503 } 504 505 if (filter_wchar && nfilters > 0) { 506 wchar_t *filter_ptr = filter_wchar; 507 filter_data = SDL_calloc(sizeof(COMDLG_FILTERSPEC), nfilters); 508 if (!filter_data) { 509 goto quit; 510 } 511 512 for (int i = 0; i < nfilters; i++) { 513 filter_data[i].pszName = filter_ptr; 514 filter_ptr += SDL_wcslen(filter_ptr) + 1; 515 filter_data[i].pszSpec = filter_ptr; 516 filter_ptr += SDL_wcslen(filter_ptr) + 1; 517 } 518 519 // assert(*filter_ptr == L'\0'); 520 } 521 522 if (title && (title_w = WIN_UTF8ToStringW(title)) == NULL) { 523 goto quit; 524 } 525 526 if (accept && (accept_w = WIN_UTF8ToStringW(accept)) == NULL) { 527 goto quit; 528 } 529 530 if (cancel && (cancel_w = WIN_UTF8ToStringW(cancel)) == NULL) { 531 goto quit; 532 } 533 534 if (default_file) { 535 default_folder_w = WIN_UTF8ToStringW(default_file); 536 537 if (!default_folder_w) { 538 goto quit; 539 } 540 541 for (wchar_t *chrptr = default_folder_w; *chrptr; chrptr++) { 542 if (*chrptr == L'/' || *chrptr == L'\\') { 543 default_file_w = chrptr; 544 } 545 } 546 547 if (!default_file_w) { 548 default_file_w = default_folder_w; 549 default_folder_w = NULL; 550 } else { 551 *default_file_w = L'\0'; 552 default_file_w++; 553 554 if (SDL_wcslen(default_file_w) == 0) { 555 default_file_w = NULL; 556 } 557 } 558 } 559 560#define CHECK(op) if (!SUCCEEDED(op)) { goto quit; } 561 562 CHECK(WIN_CoInitialize()); 563 564 co_init = true; 565 566 CHECK(CoCreateInstance(is_save ? &SDL_CLSID_FileSaveDialog : &SDL_CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, &SDL_IID_IFileDialog, (void**)&pFileDialog)); 567 CHECK(pFileDialog->lpVtbl->QueryInterface(pFileDialog, &SDL_IID_IFileDialog2, (void**)&pFileDialog2)); 568 569 if (allow_many) { 570 CHECK(pFileDialog->lpVtbl->QueryInterface(pFileDialog, &SDL_IID_IFileOpenDialog, (void**)&pFileOpenDialog)); 571 } 572 573 CHECK(pFileDialog2->lpVtbl->GetOptions(pFileDialog2, &pfos)); 574 575 pfos |= FOS_NOCHANGEDIR; 576 if (allow_many) pfos |= FOS_ALLOWMULTISELECT; 577 if (is_save) pfos |= FOS_OVERWRITEPROMPT; 578 if (is_folder) pfos |= FOS_PICKFOLDERS; 579 580 CHECK(pFileDialog2->lpVtbl->SetOptions(pFileDialog2, pfos)); 581 582 if (cancel_w) { 583 CHECK(pFileDialog2->lpVtbl->SetCancelButtonLabel(pFileDialog2, cancel_w)); 584 } 585 586 if (accept_w) { 587 CHECK(pFileDialog->lpVtbl->SetOkButtonLabel(pFileDialog, accept_w)); 588 } 589 590 if (title_w) { 591 CHECK(pFileDialog->lpVtbl->SetTitle(pFileDialog, title_w)); 592 } 593 594 if (filter_data) { 595 CHECK(pFileDialog->lpVtbl->SetFileTypes(pFileDialog, nfilters, filter_data)); 596 } 597 598 if (default_folder_w) { 599 CHECK(pSHCreateItemFromParsingName(default_folder_w, NULL, &IID_IShellItem, (void**)&pFolderItem)); 600 CHECK(pFileDialog->lpVtbl->SetFolder(pFileDialog, pFolderItem)); 601 } 602 603 if (default_file_w) { 604 CHECK(pFileDialog->lpVtbl->SetFileName(pFileDialog, default_file_w)); 605 } 606 607 if (parent) { 608 HWND window = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(parent), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); 609 610 HRESULT hr = pFileDialog->lpVtbl->Show(pFileDialog, window); 611 612 if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) { 613 const char * const results[] = { NULL }; 614 UINT selected_filter; 615 616 // This is a one-based index, not zero-based. Doc link in similar comment below 617 CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); 618 callback(userdata, results, selected_filter - 1); 619 success = true; 620 goto quit; 621 } else if (!SUCCEEDED(hr)) { 622 goto quit; 623 } 624 } else { 625 HRESULT hr = pFileDialog->lpVtbl->Show(pFileDialog, NULL); 626 627 if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) { 628 const char * const results[] = { NULL }; 629 UINT selected_filter; 630 631 // This is a one-based index, not zero-based. Doc link in similar comment below 632 CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); 633 callback(userdata, results, selected_filter - 1); 634 success = true; 635 goto quit; 636 } else if (!SUCCEEDED(hr)) { 637 goto quit; 638 } 639 } 640 641 if (allow_many) { 642 DWORD nResults; 643 UINT selected_filter; 644 645 CHECK(pFileOpenDialog->lpVtbl->GetResults(pFileOpenDialog, &pItemArray)); 646 CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); 647 CHECK(pItemArray->lpVtbl->GetCount(pItemArray, &nResults)); 648 649 files = SDL_calloc(nResults + 1, sizeof(char*)); 650 if (!files) { 651 goto quit; 652 } 653 char** files_ptr = files; 654 655 for (DWORD i = 0; i < nResults; i++) { 656 CHECK(pItemArray->lpVtbl->GetItemAt(pItemArray, i, &pItem)); 657 CHECK(pItem->lpVtbl->GetDisplayName(pItem, SIGDN_FILESYSPATH, &filePath)); 658 659 *(files_ptr++) = WIN_StringToUTF8(filePath); 660 661 CoTaskMemFree(filePath); 662 filePath = NULL; 663 pItem->lpVtbl->Release(pItem); 664 pItem = NULL; 665 } 666 667 callback(userdata, (const char * const *) files, selected_filter - 1); 668 success = true; 669 } else { 670 // This is a one-based index, not zero-based. 671 // https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-getfiletypeindex#parameters 672 UINT selected_filter; 673 674 CHECK(pFileDialog->lpVtbl->GetResult(pFileDialog, &pItem)); 675 CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter)); 676 CHECK(pItem->lpVtbl->GetDisplayName(pItem, SIGDN_FILESYSPATH, &filePath)); 677 678 char *file = WIN_StringToUTF8(filePath); 679 if (!file) { 680 goto quit; 681 } 682 const char * const results[] = { file, NULL }; 683 callback(userdata, results, selected_filter - 1); 684 success = true; 685 SDL_free(file); 686 } 687 688 success = true; 689 690#undef CHECK 691 692quit: 693 if (!success) { 694 WIN_SetError("dialogg"); 695 callback(userdata, NULL, -1); 696 } 697 698 if (co_init) { 699 WIN_CoUninitialize(); 700 } 701 702 if (pFileOpenDialog) { 703 pFileOpenDialog->lpVtbl->Release(pFileOpenDialog); 704 } 705 706 if (pFileDialog2) { 707 pFileDialog2->lpVtbl->Release(pFileDialog2); 708 } 709 710 if (pFileDialog) { 711 pFileDialog->lpVtbl->Release(pFileDialog); 712 } 713 714 if (pItem) { 715 pItem->lpVtbl->Release(pItem); 716 } 717 718 if (pFolderItem) { 719 pFolderItem->lpVtbl->Release(pFolderItem); 720 } 721 722 if (pItemArray) { 723 pItemArray->lpVtbl->Release(pItemArray); 724 } 725 726 if (filePath) { 727 CoTaskMemFree(filePath); 728 } 729 730 // If both default_file_w and default_folder_w are non-NULL, then 731 // default_file_w is a pointer into default_folder_w. 732 if (default_folder_w) { 733 SDL_free(default_folder_w); 734 } else { 735 SDL_free(default_file_w); 736 } 737 738 SDL_free(title_w); 739 740 SDL_free(accept_w); 741 742 SDL_free(cancel_w); 743 744 SDL_free(filter_data); 745 746 if (files) { 747 for (char** files_ptr = files; *files_ptr; files_ptr++) { 748 SDL_free(*files_ptr); 749 } 750 SDL_free(files); 751 } 752 753 return success; 754} 755 756// TODO: The new version of file dialogs 757void windows_ShowFileDialog(void *ptr) 758{ 759 760 winArgs *args = (winArgs *) ptr; 761 bool is_save = args->is_save; 762 const char *default_file = args->default_file; 763 SDL_Window *parent = args->parent; 764 DWORD flags = args->flags; 765 bool allow_many = args->allow_many; 766 SDL_DialogFileCallback callback = args->callback; 767 void *userdata = args->userdata; 768 const char *title = args->title; 769 const char *accept = args->accept; 770 const char *cancel = args->cancel; 771 wchar_t *filter_wchar = args->filters_str; 772 int nfilters = args->nfilters; 773 774 if (windows_ShowModernFileFolderDialog(is_save ? SDL_FILEDIALOG_SAVEFILE : SDL_FILEDIALOG_OPENFILE, default_file, parent, allow_many, callback, userdata, title, accept, cancel, filter_wchar, nfilters)) { 775 return; 776 } 777 778 /* GetOpenFileName and GetSaveFileName have the same signature 779 (yes, LPOPENFILENAMEW even for the save dialog) */ 780 typedef BOOL (WINAPI *pfnGetAnyFileNameW)(LPOPENFILENAMEW); 781 typedef DWORD (WINAPI *pfnCommDlgExtendedError)(void); 782 HMODULE lib = LoadLibraryW(L"Comdlg32.dll"); 783 pfnGetAnyFileNameW pGetAnyFileName = NULL; 784 pfnCommDlgExtendedError pCommDlgExtendedError = NULL; 785 786 if (lib) { 787 pGetAnyFileName = (pfnGetAnyFileNameW) GetProcAddress(lib, is_save ? "GetSaveFileNameW" : "GetOpenFileNameW"); 788 pCommDlgExtendedError = (pfnCommDlgExtendedError) GetProcAddress(lib, "CommDlgExtendedError"); 789 } else { 790 SDL_SetError("Couldn't load Comdlg32.dll"); 791 callback(userdata, NULL, -1); 792 return; 793 } 794 795 if (!pGetAnyFileName) { 796 SDL_SetError("Couldn't load GetOpenFileName/GetSaveFileName from library"); 797 callback(userdata, NULL, -1); 798 return; 799 } 800 801 if (!pCommDlgExtendedError) { 802 SDL_SetError("Couldn't load CommDlgExtendedError from library"); 803 callback(userdata, NULL, -1); 804 return; 805 } 806 807 HWND window = NULL; 808 809 if (parent) { 810 window = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(parent), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); 811 } 812 813 wchar_t *filebuffer; // lpstrFile 814 wchar_t initfolder[MAX_PATH] = L""; // lpstrInitialDir 815 816 /* If SELECTLIST_SIZE is too large, putting filebuffer on the stack might 817 cause an overflow */ 818 filebuffer = (wchar_t *) SDL_malloc(SELECTLIST_SIZE * sizeof(wchar_t)); 819 820 // Necessary for the return code below 821 SDL_memset(filebuffer, 0, SELECTLIST_SIZE * sizeof(wchar_t)); 822 823 if (default_file) { 824 /* On Windows 10, 11 and possibly others, lpstrFile can be initialized 825 with a path and the dialog will start at that location, but *only if 826 the path contains a filename*. If it ends with a folder (directory 827 separator), it fails with 0x3002 (12290) FNERR_INVALIDFILENAME. For 828 that specific case, lpstrInitialDir must be used instead, but just 829 for that case, because lpstrInitialDir doesn't support file names. 830 831 On top of that, lpstrInitialDir hides a special algorithm that 832 decides which folder to actually use as starting point, which may or 833 may not be the one provided, or some other unrelated folder. Also, 834 the algorithm changes between platforms. Assuming the documentation 835 is correct, the algorithm is there under 'lpstrInitialDir': 836 837 https://learn.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-openfilenamew 838 839 Finally, lpstrFile does not support forward slashes. lpstrInitialDir 840 does, though. */ 841 842 char last_c = default_file[SDL_strlen(default_file) - 1]; 843 844 if (last_c == '\\' || last_c == '/') { 845 MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, default_file, -1, initfolder, MAX_PATH); 846 } else { 847 MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, default_file, -1, filebuffer, MAX_PATH); 848 849 for (int i = 0; i < SELECTLIST_SIZE; i++) { 850 if (filebuffer[i] == L'/') { 851 filebuffer[i] = L'\\'; 852 } 853 } 854 } 855 } 856 857 wchar_t *title_w = NULL; 858 859 if (title) { 860 title_w = WIN_UTF8ToStringW(title); 861 if (!title_w) { 862 SDL_free(filebuffer); 863 callback(userdata, NULL, -1); 864 return; 865 } 866 } 867 868 OPENFILENAMEW dialog; 869 dialog.lStructSize = sizeof(OPENFILENAME); 870 dialog.hwndOwner = window; 871 dialog.hInstance = 0; 872 dialog.lpstrFilter = filter_wchar; 873 dialog.lpstrCustomFilter = NULL; 874 dialog.nMaxCustFilter = 0; 875 dialog.nFilterIndex = 0; 876 dialog.lpstrFile = filebuffer; 877 dialog.nMaxFile = SELECTLIST_SIZE; 878 dialog.lpstrFileTitle = NULL; 879 dialog.lpstrInitialDir = *initfolder ? initfolder : NULL; 880 dialog.lpstrTitle = title_w; 881 dialog.Flags = flags | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_NOCHANGEDIR; 882 dialog.nFileOffset = 0; 883 dialog.nFileExtension = 0; 884 dialog.lpstrDefExt = NULL; 885 dialog.lCustData = 0; 886 dialog.lpfnHook = NULL; 887 dialog.lpTemplateName = NULL; 888 // Skipped many mac-exclusive and reserved members 889 dialog.FlagsEx = 0; 890 891 BOOL result = pGetAnyFileName(&dialog); 892 893 SDL_free(title_w); 894 895 if (result) { 896 if (!(flags & OFN_ALLOWMULTISELECT)) { 897 // File is a C string stored in dialog.lpstrFile 898 char *chosen_file = WIN_StringToUTF8W(dialog.lpstrFile); 899 const char *opts[2] = { chosen_file, NULL }; 900 callback(userdata, opts, getFilterIndex(dialog.nFilterIndex)); 901 SDL_free(chosen_file); 902 } else { 903 /* File is either a C string if the user chose a single file, else 904 it's a series of strings formatted like: 905 906 "C:\\path\\to\\folder\0filename1.ext\0filename2.ext\0\0" 907 908 The code below will only stop on a double NULL in all cases, so 909 it is important that the rest of the buffer has been zeroed. */ 910 char chosen_folder[MAX_PATH]; 911 char chosen_file[MAX_PATH]; 912 wchar_t *file_ptr = dialog.lpstrFile; 913 size_t nfiles = 0; 914 size_t chosen_folder_size; 915 char **chosen_files_list = (char **) SDL_malloc(sizeof(char *) * (nfiles + 1)); 916 917 if (!chosen_files_list) { 918 callback(userdata, NULL, -1); 919 SDL_free(filebuffer); 920 return; 921 } 922 923 chosen_files_list[nfiles] = NULL; 924 925 if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_folder, MAX_PATH, NULL, NULL) == 0) { 926 SDL_SetError("Path too long or invalid character in path"); 927 SDL_free(chosen_files_list); 928 callback(userdata, NULL, -1); 929 SDL_free(filebuffer); 930 return; 931 } 932 933 chosen_folder_size = SDL_strlen(chosen_folder); 934 SDL_strlcpy(chosen_file, chosen_folder, MAX_PATH); 935 chosen_file[chosen_folder_size] = '\\'; 936 937 file_ptr += SDL_wcslen(file_ptr) + 1; 938 939 while (*file_ptr) { 940 nfiles++; 941 char **new_cfl = (char **) SDL_realloc(chosen_files_list, sizeof(char *) * (nfiles + 1)); 942 943 if (!new_cfl) { 944 for (size_t i = 0; i < nfiles - 1; i++) { 945 SDL_free(chosen_files_list[i]); 946 } 947 948 SDL_free(chosen_files_list); 949 callback(userdata, NULL, -1); 950 SDL_free(filebuffer); 951 return; 952 } 953 954 chosen_files_list = new_cfl; 955 chosen_files_list[nfiles] = NULL; 956 957 int diff = ((int) chosen_folder_size) + 1; 958 959 if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_file + diff, MAX_PATH - diff, NULL, NULL) == 0) { 960 SDL_SetError("Path too long or invalid character in path"); 961 962 for (size_t i = 0; i < nfiles - 1; i++) { 963 SDL_free(chosen_files_list[i]); 964 } 965 966 SDL_free(chosen_files_list); 967 callback(userdata, NULL, -1); 968 SDL_free(filebuffer); 969 return; 970 } 971 972 file_ptr += SDL_wcslen(file_ptr) + 1; 973 974 chosen_files_list[nfiles - 1] = SDL_strdup(chosen_file); 975 976 if (!chosen_files_list[nfiles - 1]) { 977 for (size_t i = 0; i < nfiles - 1; i++) { 978 SDL_free(chosen_files_list[i]); 979 } 980 981 SDL_free(chosen_files_list); 982 callback(userdata, NULL, -1); 983 SDL_free(filebuffer); 984 return; 985 } 986 } 987 988 // If the user chose only one file, it's all just one string 989 if (nfiles == 0) { 990 nfiles++; 991 char **new_cfl = (char **) SDL_realloc(chosen_files_list, sizeof(char *) * (nfiles + 1)); 992 993 if (!new_cfl) { 994 SDL_free(chosen_files_list); 995 callback(userdata, NULL, -1); 996 SDL_free(filebuffer); 997 return; 998 } 999 1000 chosen_files_list = new_cfl; 1001 chosen_files_list[nfiles] = NULL; 1002 chosen_files_list[nfiles - 1] = SDL_strdup(chosen_folder); 1003 1004 if (!chosen_files_list[nfiles - 1]) { 1005 SDL_free(chosen_files_list); 1006 callback(userdata, NULL, -1); 1007 SDL_free(filebuffer); 1008 return; 1009 } 1010 } 1011 1012 callback(userdata, (const char * const *) chosen_files_list, getFilterIndex(dialog.nFilterIndex)); 1013 1014 for (size_t i = 0; i < nfiles; i++) { 1015 SDL_free(chosen_files_list[i]); 1016 } 1017 1018 SDL_free(chosen_files_list); 1019 } 1020 } else { 1021 DWORD error = pCommDlgExtendedError(); 1022 // Error code 0 means the user clicked the cancel button. 1023 if (error == 0) { 1024 /* Unlike SDL's handling of errors, Windows does reset the error 1025 code to 0 after calling GetOpenFileName if another Windows 1026 function before set a different error code, so it's safe to 1027 check for success. */ 1028 const char *opts[1] = { NULL }; 1029 callback(userdata, opts, getFilterIndex(dialog.nFilterIndex)); 1030 } else { 1031 SDL_SetError("Windows error, CommDlgExtendedError: %ld", pCommDlgExtendedError()); 1032 callback(userdata, NULL, -1); 1033 } 1034 } 1035 1036 SDL_free(filebuffer); 1037} 1038 1039int windows_file_dialog_thread(void *ptr) 1040{ 1041 windows_ShowFileDialog(ptr); 1042 freeWinArgs(ptr); 1043 return 0; 1044} 1045 1046int CALLBACK browse_callback_proc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) 1047{ 1048 switch (uMsg) { 1049 case BFFM_INITIALIZED: 1050 if (lpData) { 1051 SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData); 1052 } 1053 break; 1054 case BFFM_SELCHANGED: 1055 break; 1056 case BFFM_VALIDATEFAILED: 1057 break; 1058 default: 1059 break; 1060 } 1061 return 0; 1062} 1063 1064void windows_ShowFolderDialog(void *ptr) 1065{ 1066 winFArgs *args = (winFArgs *) ptr; 1067 SDL_Window *window = args->parent; 1068 SDL_DialogFileCallback callback = args->callback; 1069 void *userdata = args->userdata; 1070 HWND parent = NULL; 1071 int allow_many = args->allow_many; 1072 char *default_folder = args->default_folder; 1073 const char *title = args->title; 1074 const char *accept = args->accept; 1075 const char *cancel = args->cancel; 1076 1077 if (windows_ShowModernFileFolderDialog(SDL_FILEDIALOG_OPENFOLDER, default_folder, window, allow_many, callback, userdata, title, accept, cancel, NULL, 0)) { 1078 return; 1079 } 1080 1081 if (window) { 1082 parent = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); 1083 } 1084 1085 wchar_t *title_w = NULL; 1086 1087 if (title) { 1088 title_w = WIN_UTF8ToStringW(title); 1089 if (!title_w) { 1090 callback(userdata, NULL, -1); 1091 return; 1092 } 1093 } 1094 1095 wchar_t buffer[MAX_PATH]; 1096 1097 BROWSEINFOW dialog; 1098 dialog.hwndOwner = parent; 1099 dialog.pidlRoot = NULL; 1100 dialog.pszDisplayName = buffer; 1101 dialog.lpszTitle = title_w; 1102 dialog.ulFlags = BIF_USENEWUI; 1103 dialog.lpfn = browse_callback_proc; 1104 dialog.lParam = (LPARAM)args->default_folder; 1105 dialog.iImage = 0; 1106 1107 LPITEMIDLIST lpItem = SHBrowseForFolderW(&dialog); 1108 1109 SDL_free(title_w); 1110 1111 if (lpItem != NULL) { 1112 SHGetPathFromIDListW(lpItem, buffer); 1113 char *chosen_file = WIN_StringToUTF8W(buffer); 1114 const char *files[2] = { chosen_file, NULL }; 1115 callback(userdata, (const char * const *) files, -1); 1116 SDL_free(chosen_file); 1117 } else { 1118 const char *files[1] = { NULL }; 1119 callback(userdata, (const char * const *) files, -1); 1120 } 1121} 1122 1123int windows_folder_dialog_thread(void *ptr) 1124{ 1125 windows_ShowFolderDialog(ptr); 1126 freeWinFArgs((winFArgs *)ptr); 1127 return 0; 1128} 1129 1130wchar_t *win_get_filters(const SDL_DialogFileFilter *filters, int nfilters) 1131{ 1132 wchar_t *filter_wchar = NULL; 1133 1134 if (filters) { 1135 // '\x01' is used in place of a null byte 1136 // suffix needs two null bytes in case the filter list is empty 1137 char *filterlist = convert_filters(filters, nfilters, clear_filt_names, 1138 "", "", "\x01\x01", "", "\x01", 1139 "\x01", "*.", ";*.", ""); 1140 1141 if (!filterlist) { 1142 return NULL; 1143 } 1144 1145 int filter_len = (int)SDL_strlen(filterlist); 1146 1147 for (char *c = filterlist; *c; c++) { 1148 if (*c == '\x01') { 1149 *c = '\0'; 1150 } 1151 } 1152 1153 int filter_wlen = MultiByteToWideChar(CP_UTF8, 0, filterlist, filter_len, NULL, 0); 1154 filter_wchar = (wchar_t *)SDL_malloc(filter_wlen * sizeof(wchar_t)); 1155 if (!filter_wchar) { 1156 SDL_free(filterlist); 1157 return NULL; 1158 } 1159 1160 MultiByteToWideChar(CP_UTF8, 0, filterlist, filter_len, filter_wchar, filter_wlen); 1161 1162 SDL_free(filterlist); 1163 } 1164 1165 return filter_wchar; 1166} 1167 1168static 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) 1169{ 1170 winArgs *args; 1171 SDL_Thread *thread; 1172 wchar_t *filters_str; 1173 1174 if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) { 1175 SDL_SetError("File dialog driver unsupported"); 1176 callback(userdata, NULL, -1); 1177 return; 1178 } 1179 1180 args = (winArgs *)SDL_malloc(sizeof(*args)); 1181 if (args == NULL) { 1182 callback(userdata, NULL, -1); 1183 return; 1184 } 1185 1186 filters_str = win_get_filters(filters, nfilters); 1187 1188 DWORD flags = 0; 1189 if (allow_many) { 1190 flags |= OFN_ALLOWMULTISELECT; 1191 } 1192 if (is_save) { 1193 flags |= OFN_OVERWRITEPROMPT; 1194 } 1195 1196 if (!filters_str && filters) { 1197 callback(userdata, NULL, -1); 1198 SDL_free(args); 1199 return; 1200 } 1201 1202 args->is_save = is_save; 1203 args->filters_str = filters_str; 1204 args->nfilters = nfilters; 1205 args->default_file = default_location ? SDL_strdup(default_location) : NULL; 1206 args->parent = window; 1207 args->flags = flags; 1208 args->allow_many = allow_many; 1209 args->callback = callback; 1210 args->userdata = userdata; 1211 args->title = title ? SDL_strdup(title) : NULL; 1212 args->accept = accept ? SDL_strdup(accept) : NULL; 1213 args->cancel = cancel ? SDL_strdup(cancel) : NULL; 1214 1215 thread = SDL_CreateThread(windows_file_dialog_thread, "SDL_Windows_ShowFileDialog", (void *) args); 1216 1217 if (thread == NULL) { 1218 callback(userdata, NULL, -1); 1219 // The thread won't have run, therefore the data won't have been freed 1220 freeWinArgs(args); 1221 return; 1222 } 1223 1224 SDL_DetachThread(thread); 1225} 1226 1227void 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) 1228{ 1229 winFArgs *args; 1230 SDL_Thread *thread; 1231 1232 if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) { 1233 SDL_SetError("File dialog driver unsupported"); 1234 callback(userdata, NULL, -1); 1235 return; 1236 } 1237 1238 args = (winFArgs *)SDL_malloc(sizeof(*args)); 1239 if (args == NULL) { 1240 callback(userdata, NULL, -1); 1241 return; 1242 } 1243 1244 args->parent = window; 1245 args->allow_many = allow_many; 1246 args->callback = callback; 1247 args->default_folder = default_location ? SDL_strdup(default_location) : NULL; 1248 args->userdata = userdata; 1249 args->title = title ? SDL_strdup(title) : NULL; 1250 args->accept = accept ? SDL_strdup(accept) : NULL; 1251 args->cancel = cancel ? SDL_strdup(cancel) : NULL; 1252 1253 thread = SDL_CreateThread(windows_folder_dialog_thread, "SDL_Windows_ShowFolderDialog", (void *) args); 1254 1255 if (thread == NULL) { 1256 callback(userdata, NULL, -1); 1257 // The thread won't have run, therefore the data won't have been freed 1258 freeWinFArgs(args); 1259 return; 1260 } 1261 1262 SDL_DetachThread(thread); 1263} 1264 1265void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) 1266{ 1267 /* The internal functions will start threads, and the properties may be freed as soon as this function returns. 1268 Save a copy of what we need before invoking the functions and starting the threads. */ 1269 SDL_Window *window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL); 1270 SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL); 1271 int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0); 1272 bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false); 1273 const char *default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL); 1274 const char *title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL); 1275 const char *accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL); 1276 const char *cancel = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_CANCEL_STRING, NULL); 1277 bool is_save = false; 1278 1279 switch (type) { 1280 case SDL_FILEDIALOG_SAVEFILE: 1281 is_save = true; 1282 SDL_FALLTHROUGH; 1283 case SDL_FILEDIALOG_OPENFILE: 1284 ShowFileDialog(callback, userdata, window, filters, nfilters, default_location, allow_many, is_save, title, accept, cancel); 1285 break; 1286 1287 case SDL_FILEDIALOG_OPENFOLDER: 1288 ShowFolderDialog(callback, userdata, window, default_location, allow_many, title, accept, cancel); 1289 break; 1290 }; 1291} 1292[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.