CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Windows/W32Util/ShellUtil.cpp
Views: 1401
#pragma warning(disable:4091) // workaround bug in VS2015 headers12#include "Windows/stdafx.h"34#include <functional>5#include <thread>67#include "Common/Data/Encoding/Utf8.h"8#include "Common/File/FileUtil.h"9#include "Common/Data/Format/PNGLoad.h"10#include "ShellUtil.h"1112#include <shlobj.h>13#include <commdlg.h>14#include <cderr.h>1516namespace W32Util {17std::string BrowseForFolder(HWND parent, std::string_view title, std::string_view initialPath) {18std::wstring titleString = ConvertUTF8ToWString(title);19return BrowseForFolder(parent, titleString.c_str(), initialPath);20}2122static int CALLBACK BrowseFolderCallback(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) {23if (uMsg == BFFM_INITIALIZED) {24LPCTSTR path = reinterpret_cast<LPCTSTR>(lpData);25::SendMessage(hwnd, BFFM_SETSELECTION, true, (LPARAM)path);26}27return 0;28}2930std::string BrowseForFolder(HWND parent, const wchar_t *title, std::string_view initialPath) {31BROWSEINFO info{};32info.hwndOwner = parent;33info.lpszTitle = title;34info.ulFlags = BIF_EDITBOX | BIF_RETURNONLYFSDIRS | BIF_USENEWUI | BIF_NEWDIALOGSTYLE;3536std::wstring initialPathW;3738if (!initialPath.empty()) {39initialPathW = ConvertUTF8ToWString(initialPath);40info.lParam = reinterpret_cast<LPARAM>(initialPathW.c_str());41info.lpfn = BrowseFolderCallback;42}4344//info.pszDisplayName45auto idList = SHBrowseForFolder(&info);46HMODULE shell32 = GetModuleHandle(L"shell32.dll");47typedef BOOL (WINAPI *SHGetPathFromIDListEx_f)(PCIDLIST_ABSOLUTE pidl, PWSTR pszPath, DWORD cchPath, GPFIDL_FLAGS uOpts);48SHGetPathFromIDListEx_f SHGetPathFromIDListEx_ = nullptr;49if (shell32)50SHGetPathFromIDListEx_ = (SHGetPathFromIDListEx_f)GetProcAddress(shell32, "SHGetPathFromIDListEx");5152std::string result;53if (SHGetPathFromIDListEx_) {54std::wstring temp;55do {56// Assume it's failing if it goes on too long.57if (temp.size() > 32768 * 10) {58temp.clear();59break;60}61temp.resize(temp.size() + MAX_PATH);62} while (SHGetPathFromIDListEx_(idList, &temp[0], (DWORD)temp.size(), GPFIDL_DEFAULT) == 0);63result = ConvertWStringToUTF8(temp);64} else {65wchar_t temp[MAX_PATH]{};66SHGetPathFromIDList(idList, temp);67result = ConvertWStringToUTF8(temp);68}6970CoTaskMemFree(idList);71return result;72}7374bool BrowseForFileName(bool _bLoad, HWND _hParent, const wchar_t *_pTitle,75const wchar_t *_pInitialFolder, const wchar_t *_pFilter, const wchar_t *_pExtension,76std::string &_strFileName) {77// Let's hope this is large enough, don't want to trigger the dialog twice...78std::wstring filenameBuffer(32768 * 10, '\0');7980OPENFILENAME ofn{ sizeof(OPENFILENAME) };8182auto resetFileBuffer = [&] {83ofn.nMaxFile = (DWORD)filenameBuffer.size();84ofn.lpstrFile = &filenameBuffer[0];85if (!_strFileName.empty())86wcsncpy(ofn.lpstrFile, ConvertUTF8ToWString(_strFileName).c_str(), filenameBuffer.size() - 1);87};8889resetFileBuffer();90ofn.lpstrInitialDir = _pInitialFolder;91ofn.lpstrFilter = _pFilter;92ofn.lpstrFileTitle = nullptr;93ofn.nMaxFileTitle = 0;94ofn.lpstrDefExt = _pExtension;95ofn.hwndOwner = _hParent;96ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER;97if (!_bLoad)98ofn.Flags |= OFN_HIDEREADONLY;99100int success = _bLoad ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn);101if (success == 0 && CommDlgExtendedError() == FNERR_BUFFERTOOSMALL) {102size_t sz = *(unsigned short *)&filenameBuffer[0];103// Documentation is unclear if this is WCHARs to CHARs.104filenameBuffer.resize(filenameBuffer.size() + sz * 2);105resetFileBuffer();106success = _bLoad ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn);107}108109if (success) {110_strFileName = ConvertWStringToUTF8(ofn.lpstrFile);111return true;112}113return false;114}115116std::vector<std::string> BrowseForFileNameMultiSelect(bool _bLoad, HWND _hParent, const wchar_t *_pTitle,117const wchar_t *_pInitialFolder, const wchar_t *_pFilter, const wchar_t *_pExtension) {118// Let's hope this is large enough, don't want to trigger the dialog twice...119std::wstring filenameBuffer(32768 * 10, '\0');120121OPENFILENAME ofn{ sizeof(OPENFILENAME) };122123auto resetFileBuffer = [&] {124ofn.nMaxFile = (DWORD)filenameBuffer.size();125ofn.lpstrFile = &filenameBuffer[0];126};127128resetFileBuffer();129ofn.lpstrInitialDir = _pInitialFolder;130ofn.lpstrFilter = _pFilter;131ofn.lpstrFileTitle = nullptr;132ofn.nMaxFileTitle = 0;133ofn.lpstrDefExt = _pExtension;134ofn.hwndOwner = _hParent;135ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER | OFN_ALLOWMULTISELECT;136if (!_bLoad)137ofn.Flags |= OFN_HIDEREADONLY;138139std::vector<std::string> files;140int success = _bLoad ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn);141if (success == 0 && CommDlgExtendedError() == FNERR_BUFFERTOOSMALL) {142size_t sz = *(unsigned short *)&filenameBuffer[0];143// Documentation is unclear if this is WCHARs to CHARs.144filenameBuffer.resize(filenameBuffer.size() + sz * 2);145resetFileBuffer();146success = _bLoad ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn);147}148149if (success) {150std::string directory = ConvertWStringToUTF8(ofn.lpstrFile);151wchar_t *temp = ofn.lpstrFile;152temp += wcslen(temp) + 1;153if (*temp == 0) {154//we only got one file155files.push_back(directory);156} else {157while (*temp) {158files.emplace_back(directory + "\\" + ConvertWStringToUTF8(temp));159temp += wcslen(temp) + 1;160}161}162}163return files;164}165166std::string UserDocumentsPath() {167std::string result;168HMODULE shell32 = GetModuleHandle(L"shell32.dll");169typedef HRESULT(WINAPI *SHGetKnownFolderPath_f)(REFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath);170SHGetKnownFolderPath_f SHGetKnownFolderPath_ = nullptr;171if (shell32)172SHGetKnownFolderPath_ = (SHGetKnownFolderPath_f)GetProcAddress(shell32, "SHGetKnownFolderPath");173if (SHGetKnownFolderPath_) {174PWSTR path = nullptr;175if (SHGetKnownFolderPath_(FOLDERID_Documents, 0, nullptr, &path) == S_OK) {176result = ConvertWStringToUTF8(path);177}178if (path)179CoTaskMemFree(path);180} else {181wchar_t path[MAX_PATH];182if (SHGetFolderPath(nullptr, CSIDL_PERSONAL, nullptr, SHGFP_TYPE_CURRENT, path) == S_OK) {183result = ConvertWStringToUTF8(path);184}185}186187return result;188}189190191// http://msdn.microsoft.com/en-us/library/aa969393.aspx192static HRESULT CreateLink(LPCWSTR lpszPathObj, LPCWSTR lpszArguments, LPCWSTR lpszPathLink, LPCWSTR lpszDesc, LPCWSTR lpszIcon, int iconIndex) {193HRESULT hres;194IShellLink *psl = nullptr;195hres = CoInitializeEx(NULL, COINIT_MULTITHREADED);196if (FAILED(hres))197return hres;198199// Get a pointer to the IShellLink interface. It is assumed that CoInitialize200// has already been called.201hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&psl);202if (SUCCEEDED(hres) && psl) {203IPersistFile *ppf = nullptr;204205// Set the path to the shortcut target and add the description.206psl->SetPath(lpszPathObj);207psl->SetArguments(lpszArguments);208psl->SetDescription(lpszDesc);209if (lpszIcon) {210psl->SetIconLocation(lpszIcon, iconIndex);211}212213// Query IShellLink for the IPersistFile interface, used for saving the214// shortcut in persistent storage.215hres = psl->QueryInterface(IID_IPersistFile, (LPVOID *)&ppf);216217if (SUCCEEDED(hres) && ppf) {218// Save the link by calling IPersistFile::Save.219hres = ppf->Save(lpszPathLink, TRUE);220ppf->Release();221}222psl->Release();223}224CoUninitialize();225226return hres;227}228229bool CreateDesktopShortcut(std::string_view argumentPath, std::string_view gameTitleStr, const Path &icoFile) {230// Get the desktop folder231wchar_t *pathbuf = new wchar_t[4096];232SHGetFolderPath(0, CSIDL_DESKTOPDIRECTORY, NULL, SHGFP_TYPE_CURRENT, pathbuf);233234std::string gameTitle(gameTitleStr);235// Sanitize the game title for banned characters.236const char bannedChars[] = "<>:\"/\\|?*";237for (size_t i = 0; i < gameTitle.size(); i++) {238for (char c : bannedChars) {239if (gameTitle[i] == c) {240gameTitle[i] = '_';241break;242}243}244}245246wcscat(pathbuf, L"\\");247wcscat(pathbuf, ConvertUTF8ToWString(gameTitle).c_str());248wcscat(pathbuf, L".lnk");249250std::wstring moduleFilename;251size_t sz;252do {253moduleFilename.resize(moduleFilename.size() + MAX_PATH);254// On failure, this will return the same value as passed in, but success will always be one lower.255sz = GetModuleFileName(nullptr, &moduleFilename[0], (DWORD)moduleFilename.size());256} while (sz >= moduleFilename.size());257moduleFilename.resize(sz);258259// Need to flip the slashes in the filename.260261std::string sanitizedArgument(argumentPath);262for (size_t i = 0; i < sanitizedArgument.size(); i++) {263if (sanitizedArgument[i] == '/') {264sanitizedArgument[i] = '\\';265}266}267268sanitizedArgument = "\"" + sanitizedArgument + "\"";269270std::wstring icon;271if (!icoFile.empty()) {272icon = icoFile.ToWString();273}274275CreateLink(moduleFilename.c_str(), ConvertUTF8ToWString(sanitizedArgument).c_str(), pathbuf, ConvertUTF8ToWString(gameTitle).c_str(), icon.empty() ? nullptr : icon.c_str(), 0);276277// TODO: Also extract the game icon and convert to .ico, put it somewhere under Memstick, and set it.278279delete[] pathbuf;280return false;281}282283// Function to create an icon file from PNG image data (these icons require Windows Vista).284// The Old New Thing comes to the rescue again! ChatGPT failed miserably.285// https://devblogs.microsoft.com/oldnewthing/20101018-00/?p=12513286// https://devblogs.microsoft.com/oldnewthing/20101022-00/?p=12473287bool CreateICOFromPNGData(const uint8_t *imageData, size_t imageDataSize, const Path &icoPath) {288if (imageDataSize <= sizeof(PNGHeaderPeek)) {289return false;290}291// Parse the PNG292PNGHeaderPeek pngHeader;293memcpy(&pngHeader, imageData, sizeof(PNGHeaderPeek));294if (pngHeader.Width() > 256 || pngHeader.Height() > 256) {295// Reject the png as an icon.296return false;297}298299struct IconHeader {300uint16_t reservedZero;301uint16_t type; // should be 1302uint16_t imageCount;303};304IconHeader hdr{ 0, 1, 1 };305struct IconDirectoryEntry {306BYTE bWidth;307BYTE bHeight;308BYTE bColorCount;309BYTE bReserved;310WORD wPlanes;311WORD wBitCount;312DWORD dwBytesInRes;313DWORD dwImageOffset;314};315IconDirectoryEntry entry{};316entry.bWidth = pngHeader.Width();317entry.bHeight = pngHeader.Height();318entry.bColorCount = 0;319entry.dwBytesInRes = (DWORD)imageDataSize;320entry.wPlanes = 1;321entry.wBitCount = 32;322entry.dwImageOffset = sizeof(hdr) + sizeof(entry);323324FILE *file = File::OpenCFile(icoPath, "wb");325if (!file) {326return false;327}328fwrite(&hdr, sizeof(hdr), 1, file);329fwrite(&entry, sizeof(entry), 1, file);330fwrite(imageData, 1, imageDataSize, file);331fclose(file);332return true;333}334335} // namespace336337338