From 4eaae9a7f8b01c8ed7cd300b39b4d8ad84d0e77c Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Thu, 2 Feb 2012 13:16:49 +0000 Subject: [PATCH] [ui] early ISO/NTFS bootable support (doesn't do anything yet) * enable ISO for ntfs * enable ISO selection with FileDialog * also move UI init to its own function --- src/resource.h | 1 + src/rufus.c | 251 +++++++++++++++++++++++++++++++++++-------------- src/rufus.h | 6 +- src/rufus.rc | 17 ++-- src/stdlg.c | 138 ++++++++++++++++++++++++++- 5 files changed, 330 insertions(+), 83 deletions(-) diff --git a/src/resource.h b/src/resource.h index 5ef4a34d..3ebb7308 100644 --- a/src/resource.h +++ b/src/resource.h @@ -54,6 +54,7 @@ #define IDC_DOSTYPE 1013 #define IDC_NBPASSES 1014 #define IDC_TEST 1015 +#define IDC_SELECT_ISO 1016 #define IDC_ISO_PROGRESS 1020 #define IDC_ISO_FILENAME 1021 #define IDC_ISO_ABORT 1022 diff --git a/src/rufus.c b/src/rufus.c index 30c4c556..cafb64a5 100644 --- a/src/rufus.c +++ b/src/rufus.c @@ -60,9 +60,12 @@ HWND hISOProgressDlg = NULL, hISOProgressBar, hISOFileName; BOOL bWithFreeDOS, bWithSyslinux; static HWND hDeviceTooltip = NULL, hFSTooltip = NULL, hProgress = NULL; +static HWND hDOS = NULL, hSelectISO = NULL, hISOToolTip = NULL; +static HICON hIconDisc; static StrArray DriveID, DriveLabel; static char szTimer[10] = "00:00:00"; static unsigned int timer; +static char* iso_path = NULL; /* * The following is used to allocate slots within the progress bar @@ -912,24 +915,133 @@ BOOL CALLBACK ISOProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) // The scanning process can be blocking for message processing => use a thread void __cdecl ISOScanThread(void* param) { - ExtractISO(ISO_IMAGE, ISO_DEST, TRUE); + int i; +// ExtractISO(ISO_IMAGE, ISO_DEST, TRUE); + PrintStatus(0, "Scanning ISO image..."); + ExtractISO(iso_path, "", TRUE); uprintf("Projected size: %lld\nHas 4GB: %s\n", iso_report.projected_size, iso_report.has_4GB_file?"TRUE":"FALSE"); + for (i=safe_strlen(iso_path); (i>=0)&&(iso_path[i] != '\\'); i--); + PrintStatus(0, "Using ISO: '%s'", &iso_path[i+1]); _endthread(); } +// Helper function to obtain a handle to a DLL +static __inline HMODULE GetDLLHandle(char* szDLLName) +{ + HMODULE h = NULL; + if ((h = GetModuleHandleA(szDLLName)) == NULL) + h = LoadLibraryA(szDLLName); + return h; +} + +void InitDialog(HWND hDlg) +{ + // MinGW fails to link those + typedef HIMAGELIST (WINAPI *ImageList_Create_t)( + int cx, + int cy, + UINT flags, + int cInitial, + int cGrow + ); + ImageList_Create_t pImageList_Create = NULL; + typedef int (WINAPI *ImageList_ReplaceIcon_t)( + HIMAGELIST himl, + int i, + HICON hicon + ); + ImageList_ReplaceIcon_t pImageList_ReplaceIcon = NULL; + struct { + HIMAGELIST himl; + RECT margin; + UINT uAlign; + } bi = {0}; // BUTTON_IMAGELIST + HINSTANCE hDllInst; + HDC hDC; + int i16; + HICON hSmallIcon, hBigIcon; + char tmp[128]; + + // Quite a burden to carry around as parameters + hMainDialog = hDlg; + hDeviceList = GetDlgItem(hDlg, IDC_DEVICE); + hCapacity = GetDlgItem(hDlg, IDC_CAPACITY); + hFileSystem = GetDlgItem(hDlg, IDC_FILESYSTEM); + hClusterSize = GetDlgItem(hDlg, IDC_CLUSTERSIZE); + hLabel = GetDlgItem(hDlg, IDC_LABEL); + hProgress = GetDlgItem(hDlg, IDC_PROGRESS); + hDOS = GetDlgItem(hDlg, IDC_DOS); + hDOSType = GetDlgItem(hDlg, IDC_DOSTYPE); + hSelectISO = GetDlgItem(hDlg, IDC_SELECT_ISO); + hNBPasses = GetDlgItem(hDlg, IDC_NBPASSES); + + // High DPI scaling + i16 = GetSystemMetrics(SM_CXSMICON); + hDC = GetDC(hDlg); + fScale = GetDeviceCaps(hDC, LOGPIXELSX) / 96.0f; + ReleaseDC(hDlg, hDC); + + // Create the title bar icon + hSmallIcon = (HICON)LoadImage(hMainInstance, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0); + SendMessage (hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hSmallIcon); + hBigIcon = (HICON)LoadImage(hMainInstance, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 32, 32, 0); + SendMessage (hDlg, WM_SETICON, ICON_BIG, (LPARAM)hBigIcon); + // Update the title if we have FreeDOS support + if (bWithFreeDOS) { + GetWindowTextA(hDlg, &tmp[15], sizeof(tmp)-15); + safe_sprintf(tmp, sizeof(tmp), "Rufus (with FreeDOS)"); + tmp[20] = ' '; + SetWindowTextA(hDlg, tmp); + } + + // Create the status line + CreateStatusBar(); + // Use maximum granularity for the progress bar + SendMessage(hProgress, PBM_SETRANGE, 0, MAX_PROGRESS<<16); + // Fill up the passes + IGNORE_RETVAL(ComboBox_AddStringU(hNBPasses, "1 Pass")); + IGNORE_RETVAL(ComboBox_AddStringU(hNBPasses, "2 Passes")); + IGNORE_RETVAL(ComboBox_AddStringU(hNBPasses, "3 Passes")); + IGNORE_RETVAL(ComboBox_AddStringU(hNBPasses, "4 Passes")); + IGNORE_RETVAL(ComboBox_SetCurSel(hNBPasses, 1)); + // Fill up the DOS type dropdown + IGNORE_RETVAL(ComboBox_SetItemData(hDOSType, ComboBox_AddStringU(hDOSType, "MS-DOS"), DT_WINME)); + IGNORE_RETVAL(ComboBox_SetItemData(hDOSType, ComboBox_AddStringU(hDOSType, "ISO"), DT_ISO)); + IGNORE_RETVAL(ComboBox_SetCurSel(hDOSType, bWithFreeDOS?DT_FREEDOS:DT_WINME)); + // Create the string array + StrArrayCreate(&DriveID, MAX_DRIVES); + StrArrayCreate(&DriveLabel, MAX_DRIVES); + // Set the quick format & create DOS disk checkboxes + CheckDlgButton(hDlg, IDC_QUICKFORMAT, BST_CHECKED); + CheckDlgButton(hDlg, IDC_DOS, BST_CHECKED); + + // Load system icons (NB: Use the excellent http://www.nirsoft.net/utils/iconsext.html to find icon IDs) + hDllInst = LoadLibraryA("shell32.dll"); + hIconDisc = (HICON)LoadImage(hDllInst, MAKEINTRESOURCE(12), IMAGE_ICON, i16, i16, LR_DEFAULTCOLOR|LR_SHARED); + + // Set a disc icon on the select ISO button + pImageList_Create = (ImageList_Create_t) GetProcAddress(GetDLLHandle("Comctl32.dll"), "ImageList_Create"); + pImageList_ReplaceIcon = (ImageList_ReplaceIcon_t) GetProcAddress(GetDLLHandle("Comctl32.dll"), "ImageList_ReplaceIcon"); + + bi.himl = pImageList_Create(i16, i16, ILC_COLOR32 | ILC_MASK, 1, 0); + pImageList_ReplaceIcon(bi.himl, -1, hIconDisc); + SetRect(&bi.margin, 0, 1, 0, 0); + bi.uAlign = 4; // BUTTON_IMAGELIST_ALIGN_CENTER + SendMessage(GetDlgItem(hDlg, IDC_SELECT_ISO), 0x1602, 0, (LPARAM)&bi); // BCM_SETIMAGELIST + + hISOToolTip = CreateTooltip(hSelectISO, "Click to select...", -1); +} + /* * Main dialog callback */ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { - HDC hDC; - HICON hSmallIcon, hBigIcon; DRAWITEMSTRUCT* pDI; - int nDeviceIndex, fs; + int nDeviceIndex, fs, dt; DWORD DeviceNum; char str[MAX_PATH], tmp[128]; static uintptr_t format_thid = -1L; - static HWND hDOS; static UINT uDOSChecked = BST_CHECKED; switch (message) { @@ -943,59 +1055,7 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA break; case WM_INITDIALOG: - hMainDialog = hDlg; - hDeviceList = GetDlgItem(hDlg, IDC_DEVICE); - hCapacity = GetDlgItem(hDlg, IDC_CAPACITY); - hFileSystem = GetDlgItem(hDlg, IDC_FILESYSTEM); - hClusterSize = GetDlgItem(hDlg, IDC_CLUSTERSIZE); - hLabel = GetDlgItem(hDlg, IDC_LABEL); - hProgress = GetDlgItem(hDlg, IDC_PROGRESS); - hDOS = GetDlgItem(hDlg, IDC_DOS); - hDOSType = GetDlgItem(hDlg, IDC_DOSTYPE); - hNBPasses = GetDlgItem(hDlg, IDC_NBPASSES); - // High DPI scaling - hDC = GetDC(hDlg); - fScale = GetDeviceCaps(hDC, LOGPIXELSX) / 96.0f; - ReleaseDC(hDlg, hDC); - // Create the title bar icon - hSmallIcon = (HICON)LoadImage(hMainInstance, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0); - SendMessage (hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hSmallIcon); - hBigIcon = (HICON)LoadImage(hMainInstance, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 32, 32, 0); - SendMessage (hDlg, WM_SETICON, ICON_BIG, (LPARAM)hBigIcon); - // Update the title if we have FreeDOS support - if (bWithFreeDOS) { - GetWindowTextA(hDlg, &tmp[15], sizeof(tmp)-15); - safe_sprintf(tmp, sizeof(tmp), "Rufus (with FreeDOS)"); - tmp[20] = ' '; - SetWindowTextA(hDlg, tmp); - } - // Create the status line - CreateStatusBar(); - // Use maximum granularity for the progress bar - SendMessage(hProgress, PBM_SETRANGE, 0, MAX_PROGRESS<<16); - // Fill up the passes - IGNORE_RETVAL(ComboBox_AddStringU(hNBPasses, "1 Pass")); - IGNORE_RETVAL(ComboBox_AddStringU(hNBPasses, "2 Passes")); - IGNORE_RETVAL(ComboBox_AddStringU(hNBPasses, "3 Passes")); - IGNORE_RETVAL(ComboBox_AddStringU(hNBPasses, "4 Passes")); - IGNORE_RETVAL(ComboBox_SetCurSel(hNBPasses, 1)); - // Fill up the DOS type dropdown - IGNORE_RETVAL(ComboBox_SetItemData(hDOSType, ComboBox_AddStringU(hDOSType, "WinMe"), DT_WINME)); - if (bWithFreeDOS) - IGNORE_RETVAL(ComboBox_SetItemData(hDOSType, ComboBox_AddStringU(hDOSType, "FreeDOS"), DT_FREEDOS)); - if (bWithSyslinux) - IGNORE_RETVAL(ComboBox_SetItemData(hDOSType, ComboBox_AddStringU(hDOSType, "Syslinux"), DT_SYSLINUX)); - IGNORE_RETVAL(ComboBox_SetCurSel(hDOSType, bWithFreeDOS?DT_FREEDOS:DT_WINME)); - if (bWithFreeDOS || bWithSyslinux) { - SetDlgItemTextA(hDlg, IDC_DOS, "Create a bootable USB drive:"); - ShowWindow(hDOSType, SW_SHOW); - } - // Create the string array - StrArrayCreate(&DriveID, MAX_DRIVES); - StrArrayCreate(&DriveLabel, MAX_DRIVES); - // Set the quick format & create DOS disk checkboxes - CheckDlgButton(hDlg, IDC_QUICKFORMAT, BST_CHECKED); - CheckDlgButton(hDlg, IDC_DOS, BST_CHECKED); + InitDialog(hDlg); GetUSBDevices(); return (INT_PTR)TRUE; @@ -1064,28 +1124,73 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA } break; case IDC_FILESYSTEM: + if (HIWORD(wParam) != CBN_SELCHANGE) + break; + fs = (int)ComboBox_GetItemData(hFileSystem, ComboBox_GetCurSel(hFileSystem)); + uprintf("fs = %d\n", fs); + SetClusterSizes(fs); + if (((fs == FS_EXFAT) || (fs <0)) && IsWindowEnabled(hDOS)) { + // unlikely to be supported by BIOSes => don't bother + IGNORE_RETVAL(ComboBox_SetCurSel(hDOSType, 0)); + uDOSChecked = IsDlgButtonChecked(hMainDialog, IDC_DOS); + CheckDlgButton(hDlg, IDC_DOS, BST_UNCHECKED); + EnableWindow(hDOS, FALSE); + EnableWindow(hDOSType, FALSE); + ShowWindow(hSelectISO, SW_HIDE); + break; + } + IGNORE_RETVAL(ComboBox_ResetContent(hDOSType)); + if ((fs == FS_FAT16) || (fs == FS_FAT32)) { + IGNORE_RETVAL(ComboBox_SetItemData(hDOSType, ComboBox_AddStringU(hDOSType, "MS-DOS"), DT_WINME)); + if (bWithFreeDOS) + IGNORE_RETVAL(ComboBox_SetItemData(hDOSType, ComboBox_AddStringU(hDOSType, "FreeDOS"), DT_FREEDOS)); + if (bWithSyslinux) + IGNORE_RETVAL(ComboBox_SetItemData(hDOSType, ComboBox_AddStringU(hDOSType, "Syslinux"), DT_SYSLINUX)); + } + IGNORE_RETVAL(ComboBox_SetItemData(hDOSType, ComboBox_AddStringU(hDOSType, "ISO"), DT_ISO)); + IGNORE_RETVAL(ComboBox_SetCurSel(hDOSType, (bWithFreeDOS && (fs != FS_NTFS))?1:0)); + if (!IsWindowEnabled(hDOS)) { + EnableWindow(hDOS, TRUE); + EnableWindow(hDOSType, TRUE); + CheckDlgButton(hDlg, IDC_DOS, uDOSChecked); + } + // Fall through to enable/disable the ISO selection + case IDC_DOSTYPE: switch (HIWORD(wParam)) { case CBN_SELCHANGE: - fs = (int)ComboBox_GetItemData(hFileSystem, ComboBox_GetCurSel(hFileSystem)); - SetClusterSizes(fs); + dt = (int)ComboBox_GetItemData(hDOSType, ComboBox_GetCurSel(hDOSType)); // Disable/Restore the DOS checkbox according to FS - if ((fs == FS_FAT16) || (fs == FS_FAT32)) { - if (!IsWindowEnabled(hDOS)) { - EnableWindow(hDOS, TRUE); - EnableWindow(hDOSType, TRUE); - CheckDlgButton(hDlg, IDC_DOS, uDOSChecked); - } + if (dt == DT_ISO) { + ShowWindow(hSelectISO, SW_SHOW); } else { - if (IsWindowEnabled(hDOS)) { - uDOSChecked = IsDlgButtonChecked(hMainDialog, IDC_DOS); - CheckDlgButton(hDlg, IDC_DOS, BST_UNCHECKED); - EnableWindow(hDOS, FALSE); - EnableWindow(hDOSType, FALSE); - } + ShowWindow(hSelectISO, SW_HIDE); } break; } break; + case IDC_SELECT_ISO: + DestroyTooltip(hISOToolTip); + safe_free(iso_path); + iso_path = FileDialog(FALSE, NULL, "*.iso", "iso", "ISO Image"); + if (iso_path == NULL) { + hISOToolTip = CreateTooltip(hSelectISO, "Click to select...", -1); + break; + } + hISOToolTip = CreateTooltip(hSelectISO, iso_path, -1); + FormatStatus = 0; + // You'd think that Windows would let you instantiate a modeless dialog wherever + // but you'd be wrong. It must be done in the main callback! + if (!IsWindow(hISOProgressDlg)) { + hISOProgressDlg = CreateDialogA(hMainInstance, MAKEINTRESOURCEA(IDD_ISO_EXTRACT), + hDlg, (DLGPROC)ISOProc); + // The window is not visible by default but takes focus => restore it + SetFocus(hDlg); + } + if (_beginthread(ISOScanThread, 0, NULL) == -1L) { + uprintf("Unable to start ISO scanning thread"); + FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|APPERR(ERROR_CANT_START_THREAD); + } + break; case IDC_START: if (format_thid != -1L) { return (INT_PTR)TRUE; @@ -1131,6 +1236,8 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA if (format_thid != -1L) { return (INT_PTR)TRUE; } + DestroyAllTooltips(); + safe_free(iso_path); PostQuitMessage(0); break; diff --git a/src/rufus.h b/src/rufus.h index 9ae1f756..e28277db 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -27,8 +27,8 @@ /* Features not ready for prime time and that may *DESTROY* your data - USE AT YOUR OWN RISKS! */ //#define RUFUS_TEST -#define ISO_DEST "D:/tmp/iso" -#define ISO_IMAGE "D:\\Incoming\\Windows 8 Preview\\WindowsDeveloperPreview-64bit-English-Developer.iso" +//#define ISO_DEST "D:/tmp/iso" +//#define ISO_IMAGE "D:\\Incoming\\Windows 8 Preview\\WindowsDeveloperPreview-64bit-English-Developer.iso" //#define ISO_IMAGE "D:\\fd11src.iso", "D:/tmp/iso" //#define ISO_IMAGE "D:\\Incoming\\GRMSDKX_EN_DVD.iso" //#define ISO_IMAGE "D:\\Incoming\\en_windows_driver_kit_3790.iso" @@ -131,6 +131,7 @@ enum dos_type { DT_WINME = 0, DT_FREEDOS, DT_SYSLINUX, + DT_ISO, DT_MAX }; @@ -192,6 +193,7 @@ extern HANDLE GetDriveHandle(DWORD DriveIndex, char* DriveLetter, BOOL bWriteAcc extern BOOL GetDriveLabel(DWORD DriveIndex, char* letter, char** label); extern BOOL UnmountDrive(HANDLE hDrive); extern BOOL CreateProgress(void); +extern char* FileDialog(BOOL save, char* path, char* filename, char* ext, char* ext_desc); __inline static BOOL UnlockDrive(HANDLE hDrive) { diff --git a/src/rufus.rc b/src/rufus.rc index 11f8213f..592e78f7 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 206, 278 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_APPWINDOW -CAPTION "Rufus v1.0.7.122" +CAPTION "Rufus v1.0.7.123" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN DEFPUSHBUTTON "Start",IDC_START,94,236,50,14 @@ -51,12 +51,13 @@ BEGIN EDITTEXT IDC_LABEL,7,131,190,13,ES_AUTOHSCROLL CONTROL "Check device for bad blocks:",IDC_BADBLOCKS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,161,101,10 CONTROL "Quick Format",IDC_QUICKFORMAT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,173,58,10 - CONTROL "Create a DOS bootable disk",IDC_DOS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,185,104,10 + CONTROL "Create a bootable disk with:",IDC_DOS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,185,101,10 LTEXT "New volume label",IDC_STATIC,9,121,105,10 CONTROL "",IDC_PROGRESS,"msctls_progress32",PBS_SMOOTH | WS_BORDER,7,210,189,9 - COMBOBOX IDC_DOSTYPE,118,183,45,30,CBS_DROPDOWNLIST | NOT WS_VISIBLE | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_DOSTYPE,118,183,45,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP COMBOBOX IDC_NBPASSES,118,159,45,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP PUSHBUTTON "Test",IDC_TEST,62,236,20,14 + PUSHBUTTON "...",IDC_SELECT_ISO,168,182,23,14,BS_ICON | NOT WS_VISIBLE END IDD_ABOUTBOX DIALOGEX 0, 0, 287, 195 @@ -70,7 +71,7 @@ BEGIN DEFPUSHBUTTON "OK",IDOK,231,175,50,14,WS_GROUP CONTROL "http://rufus.akeo.ie",IDC_ABOUT_RUFUS_URL, "SysLink",WS_TABSTOP,46,47,114,9 - LTEXT "Version 1.0.7 (Build 122)",IDC_STATIC,46,19,78,8 + LTEXT "Version 1.0.7 (Build 123)",IDC_STATIC,46,19,78,8 PUSHBUTTON "License...",IDC_ABOUT_LICENSE,46,175,50,14,WS_GROUP EDITTEXT IDC_ABOUT_COPYRIGHTS,46,107,235,63,ES_MULTILINE | ES_READONLY | WS_VSCROLL LTEXT "Report bugs or request enhancements at:",IDC_STATIC,46,66,187,8 @@ -221,8 +222,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,0,7,122 - PRODUCTVERSION 1,0,7,122 + FILEVERSION 1,0,7,123 + PRODUCTVERSION 1,0,7,123 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -239,13 +240,13 @@ BEGIN BEGIN VALUE "CompanyName", "akeo.ie" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "1.0.7.122" + VALUE "FileVersion", "1.0.7.123" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "http://www.gnu.org/copyleft/gpl.html" VALUE "OriginalFilename", "rufus.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "1.0.7.122" + VALUE "ProductVersion", "1.0.7.123" END END BLOCK "VarFileInfo" diff --git a/src/stdlg.c b/src/stdlg.c index 23aeb8b2..7e9dc473 100644 --- a/src/stdlg.c +++ b/src/stdlg.c @@ -314,6 +314,142 @@ fallback: } } +/* + * Return the UTF8 path of a file selected through a load or save dialog + * Will use the newer IFileOpenDialog if *compiled* for Vista or later + * All string parameters are UTF-8 + */ +char* FileDialog(BOOL save, char* path, char* filename, char* ext, char* ext_desc) +{ + DWORD tmp; + OPENFILENAMEA ofn; + char selected_name[MAX_PATH]; + char* ext_string = NULL; + size_t i, ext_strlen; + BOOL r; + char* filepath = NULL; + +#if (_WIN32_WINNT >= 0x0600) // Vista and later + HRESULT hr = FALSE; + IFileDialog *pfd; + IShellItem *psiResult; + COMDLG_FILTERSPEC filter_spec[2]; + char* ext_filter; + wchar_t *wpath = NULL, *wfilename = NULL; + IShellItem *si_path = NULL; // Automatically freed + + INIT_VISTA_SHELL32; + if (IS_VISTA_SHELL32_AVAILABLE) { + // Setup the file extension filter table + ext_filter = (char*)malloc(strlen(ext)+3); + if (ext_filter != NULL) { + safe_sprintf(ext_filter, strlen(ext)+3, "*.%s", ext); + filter_spec[0].pszSpec = utf8_to_wchar(ext_filter); + safe_free(ext_filter); + filter_spec[0].pszName = utf8_to_wchar(ext_desc); + filter_spec[1].pszSpec = L"*.*"; + filter_spec[1].pszName = L"All files"; + } + + hr = CoCreateInstance(save?&CLSID_FileSaveDialog:&CLSID_FileOpenDialog, NULL, CLSCTX_INPROC, + &IID_IFileDialog, (LPVOID)&pfd); + + if (FAILED(hr)) { + uprintf("CoCreateInstance for FileOpenDialog failed: error %X\n", hr); + pfd = NULL; // Just in case + goto fallback; + } + + // Set the file extension filters + pfd->lpVtbl->SetFileTypes(pfd, 2, filter_spec); + + // Set the default directory + wpath = utf8_to_wchar(path); + hr = (*pSHCreateItemFromParsingName)(wpath, NULL, &IID_IShellItem, (LPVOID) &si_path); + if (SUCCEEDED(hr)) { + pfd->lpVtbl->SetFolder(pfd, si_path); + } + safe_free(wpath); + + // Set the default filename + wfilename = utf8_to_wchar(filename); + if (wfilename != NULL) { + pfd->lpVtbl->SetFileName(pfd, wfilename); + } + + // Display the dialog + hr = pfd->lpVtbl->Show(pfd, hMainDialog); + + // Cleanup + safe_free(wfilename); + safe_free(filter_spec[0].pszSpec); + safe_free(filter_spec[0].pszName); + + if (SUCCEEDED(hr)) { + // Obtain the result of the user's interaction with the dialog. + hr = pfd->lpVtbl->GetResult(pfd, &psiResult); + if (SUCCEEDED(hr)) { + hr = psiResult->lpVtbl->GetDisplayName(psiResult, SIGDN_FILESYSPATH, &wpath); + if (SUCCEEDED(hr)) { + filepath = wchar_to_utf8(wpath); + CoTaskMemFree(wpath); + } + psiResult->lpVtbl->Release(psiResult); + } + } else if ((hr & 0xFFFF) != ERROR_CANCELLED) { + // If it's not a user cancel, assume the dialog didn't show and fallback + uprintf("could not show FileOpenDialog: error %X\n", hr); + goto fallback; + } + pfd->lpVtbl->Release(pfd); + return filepath; + } + +fallback: + if (pfd != NULL) { + pfd->lpVtbl->Release(pfd); + } +#endif + + memset(&ofn, 0, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = hMainDialog; + // File name + safe_strcpy(selected_name, MAX_PATH, filename); + ofn.lpstrFile = selected_name; + ofn.nMaxFile = MAX_PATH; + // Set the file extension filters + ext_strlen = strlen(ext_desc) + 2*strlen(ext) + sizeof(" (*.)\0*.\0All Files (*.*)\0*.*\0\0"); + ext_string = (char*)malloc(ext_strlen); + safe_sprintf(ext_string, ext_strlen, "%s (*.%s)\r*.%s\rAll Files (*.*)\r*.*\r\0", ext_desc, ext, ext); + // Microsoft could really have picked a better delimiter! + for (i=0; i