// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #if PLATFORM_WINDOWS #include "WindowsFileSystem.h" #include "Engine/Platform/File.h" #include "Engine/Platform/Window.h" #include "Engine/Platform/Windows/ComPtr.h" #include "Engine/Core/Types/StringView.h" #include "../Win32/IncludeWindowsHeaders.h" // Hack this stuff (the problem is that GDI has function named Rectangle -> like one of the Flax core types) #define _PRSHT_H_ #define __shobjidl_h__ #define LPFNADDPROPSHEETPAGE void* #include #include #include namespace Windows { typedef UINT_PTR (CALLBACK *LPOFNHOOKPROC)(HWND, UINT, WPARAM, LPARAM); typedef struct tagOFNW { DWORD lStructSize; HWND hwndOwner; HINSTANCE hInstance; LPCWSTR lpstrFilter; LPWSTR lpstrCustomFilter; DWORD nMaxCustFilter; DWORD nFilterIndex; LPWSTR lpstrFile; DWORD nMaxFile; LPWSTR lpstrFileTitle; DWORD nMaxFileTitle; LPCWSTR lpstrInitialDir; LPCWSTR lpstrTitle; DWORD Flags; WORD nFileOffset; WORD nFileExtension; LPCWSTR lpstrDefExt; LPARAM lCustData; LPOFNHOOKPROC lpfnHook; LPCWSTR lpTemplateName; void* pvReserved; DWORD dwReserved; DWORD FlagsEx; } OPENFILENAMEW, *LPOPENFILENAMEW; typedef OPENFILENAMEW OPENFILENAME; WIN_API BOOL WIN_API_CALLCONV GetOpenFileNameW(LPOPENFILENAMEW); WIN_API BOOL WIN_API_CALLCONV GetSaveFileNameW(LPOPENFILENAMEW); #define OFN_READONLY 0x00000001 #define OFN_OVERWRITEPROMPT 0x00000002 #define OFN_HIDEREADONLY 0x00000004 #define OFN_NOCHANGEDIR 0x00000008 #define OFN_SHOWHELP 0x00000010 #define OFN_ENABLEHOOK 0x00000020 #define OFN_ENABLETEMPLATE 0x00000040 #define OFN_ENABLETEMPLATEHANDLE 0x00000080 #define OFN_NOVALIDATE 0x00000100 #define OFN_ALLOWMULTISELECT 0x00000200 #define OFN_EXTENSIONDIFFERENT 0x00000400 #define OFN_PATHMUSTEXIST 0x00000800 #define OFN_FILEMUSTEXIST 0x00001000 #define OFN_CREATEPROMPT 0x00002000 #define OFN_SHAREAWARE 0x00004000 #define OFN_NOREADONLYRETURN 0x00008000 #define OFN_NOTESTFILECREATE 0x00010000 #define OFN_NONETWORKBUTTON 0x00020000 #define OFN_NOLONGNAMES 0x00040000 #define OFN_EXPLORER 0x00080000 #define OFN_NODEREFERENCELINKS 0x00100000 #define OFN_LONGNAMES 0x00200000 #define OFN_ENABLEINCLUDENOTIFY 0x00400000 #define OFN_ENABLESIZING 0x00800000 #define OFN_DONTADDTORECENT 0x02000000 #define OFN_FORCESHOWHIDDEN 0x10000000 } static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) { if (uMsg == BFFM_INITIALIZED) { if (lParam) { SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lParam); } } return 0; } bool WindowsFileSystem::MoveFileToRecycleBin(const StringView& path) { ASSERT(path.Length() < MAX_PATH); Char pathNullNull[MAX_PATH + 1]; Platform::MemoryCopy(pathNullNull, *path, sizeof(Char) * path.Length() + 1); pathNullNull[path.Length()] = 0; pathNullNull[path.Length() + 1] = 0; SHFILEOPSTRUCT op; op.hwnd = nullptr; op.wFunc = FO_DELETE; op.pFrom = pathNullNull; op.pTo = nullptr; op.fFlags = FOF_ALLOWUNDO | FOF_NO_UI; op.fAnyOperationsAborted = FALSE; op.hNameMappings = nullptr; op.lpszProgressTitle = nullptr; return SHFileOperation(&op) != 0; } bool SameFile(HANDLE h1, HANDLE h2) { BY_HANDLE_FILE_INFORMATION bhfi1 = { 0 }; BY_HANDLE_FILE_INFORMATION bhfi2 = { 0 }; if (::GetFileInformationByHandle(h1, &bhfi1) && ::GetFileInformationByHandle(h2, &bhfi2)) { return ((bhfi1.nFileIndexHigh == bhfi2.nFileIndexHigh) && (bhfi1.nFileIndexLow == bhfi2.nFileIndexLow) && (bhfi1.dwVolumeSerialNumber == bhfi2.dwVolumeSerialNumber)); } return false; } bool WindowsFileSystem::AreFilePathsEqual(const StringView& path1, const StringView& path2) { if (path1.Compare(path2, StringSearchCase::CaseSensitive) == 0) return true; // Normalize file paths String filename1(path1); String filename2(path2); NormalizePath(filename1); NormalizePath(filename2); HANDLE file1 = CreateFileW(*filename1, GENERIC_READ, (DWORD)FileShare::All, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); HANDLE file2 = CreateFileW(*filename2, GENERIC_READ, (DWORD)FileShare::All, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); bool result = SameFile(file1, file2); CloseHandle(file1); CloseHandle(file2); return result; } void WindowsFileSystem::GetSpecialFolderPath(const SpecialFolder type, String& result) { KNOWNFOLDERID rfid; switch (type) { case SpecialFolder::Desktop: rfid = FOLDERID_Desktop; break; case SpecialFolder::Documents: rfid = FOLDERID_Documents; break; case SpecialFolder::Pictures: rfid = FOLDERID_Pictures; break; case SpecialFolder::AppData: rfid = FOLDERID_RoamingAppData; break; case SpecialFolder::LocalAppData: rfid = FOLDERID_LocalAppData; break; case SpecialFolder::ProgramData: rfid = FOLDERID_ProgramData; break; case SpecialFolder::Temporary: { Char buffer[MAX_PATH]; if (GetTempPathW(MAX_PATH, buffer)) { result = buffer; NormalizePath(result); } return; } } PWSTR path = nullptr; HRESULT hr = SHGetKnownFolderPath(rfid, 0, nullptr, &path); if (SUCCEEDED(hr)) { result = path; NormalizePath(result); } CoTaskMemFree(path); } bool WindowsFileSystem::ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array& filenames) { bool result = true; // Allocate memory for the filenames const int32 maxFilenamesSize = (multiSelect ? 200 : 2) * MAX_PATH; Array fileNamesBuffer; fileNamesBuffer.Resize(maxFilenamesSize); fileNamesBuffer[0] = 0; // Setup description Windows::OPENFILENAME of; ZeroMemory(&of, sizeof(of)); of.lStructSize = sizeof(of); of.lpstrFilter = filter.HasChars() ? filter.Get() : nullptr; of.lpstrFile = fileNamesBuffer.Get(); of.nMaxFile = maxFilenamesSize; of.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_ENABLESIZING | OFN_NOCHANGEDIR; of.lpstrTitle = title.HasChars() ? title.Get() : nullptr; of.lpstrInitialDir = initialDirectory.HasChars() ? initialDirectory.Get() : nullptr; if (parentWindow) of.hwndOwner = static_cast(parentWindow->GetNativePtr()); if (multiSelect) of.Flags |= OFN_ALLOWMULTISELECT; // Show dialog if (GetOpenFileNameW(&of) != 0) { // Get filenames Char* ptr = of.lpstrFile; ptr[of.nFileOffset - 1] = 0; String directory = String(ptr); ptr += of.nFileOffset; while (*ptr) { filenames.Add(directory / ptr); ptr += lstrlenW(ptr); if (multiSelect) ptr++; } result = false; } return result; } bool WindowsFileSystem::ShowSaveFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array& filenames) { bool result = true; // Allocate memory for the filenames int32 maxFilenamesSize = (multiSelect ? 200 : 2) * MAX_PATH; Array fileNamesBuffer; fileNamesBuffer.Resize(maxFilenamesSize); fileNamesBuffer[0] = 0; // Setup description Windows::OPENFILENAME of; ZeroMemory(&of, sizeof(of)); of.lStructSize = sizeof(of); of.lpstrFilter = filter.HasChars() ? filter.Get() : nullptr; of.lpstrFile = fileNamesBuffer.Get(); of.nMaxFile = maxFilenamesSize; of.Flags = OFN_EXPLORER | OFN_ENABLESIZING | OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR; of.lpstrTitle = title.HasChars() ? title.Get() : nullptr; of.lpstrInitialDir = initialDirectory.HasChars() ? initialDirectory.Get() : nullptr; if (parentWindow) of.hwndOwner = static_cast(parentWindow->GetNativePtr()); if (multiSelect) of.Flags |= OFN_ALLOWMULTISELECT; // Show dialog if (GetSaveFileNameW(&of) != 0) { // Get filenames Char* ptr = of.lpstrFile; ptr[of.nFileOffset - 1] = 0; String directory = String(ptr); ptr += of.nFileOffset; while (*ptr) { filenames.Add(directory / ptr); ptr += lstrlenW(ptr); if (multiSelect) ptr++; } result = false; } return result; } bool WindowsFileSystem::ShowBrowseFolderDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& title, String& path) { bool result = true; // Randomly generated GUID used for storing the last location of this dialog const Guid folderGuid(0x53890ed9, 0xa55e47ba, 0xa970bdae, 0x72acedff); ComPtr fd; if (SUCCEEDED(CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&fd)))) { DWORD options; fd->GetOptions(&options); fd->SetOptions(options | FOS_PICKFOLDERS | FOS_NOCHANGEDIR); if (title.HasChars()) fd->SetTitle(title.Get()); // Associate the last selected folder with this GUID instead of overwriting the global one fd->SetClientGuid(*reinterpret_cast(&folderGuid)); ComPtr defaultFolder; if (SUCCEEDED(SHCreateItemFromParsingName(initialDirectory.Get(), NULL, IID_PPV_ARGS(&defaultFolder)))) fd->SetFolder(defaultFolder); if (SUCCEEDED(fd->Show(parentWindow->GetHWND()))) { ComPtr si; if (SUCCEEDED(fd->GetResult(&si))) { LPWSTR resultPath; if (SUCCEEDED(si->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &resultPath))) { path = resultPath; CoTaskMemFree(resultPath); result = false; } } } } return result; } bool WindowsFileSystem::ShowFileExplorer(const StringView& path) { return Platform::StartProcess(path, StringView::Empty, StringView::Empty) != 0; } #endif