CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/Windows/W32Util/ShellUtil.cpp
Views: 1401
1
#pragma warning(disable:4091) // workaround bug in VS2015 headers
2
3
#include "Windows/stdafx.h"
4
5
#include <functional>
6
#include <thread>
7
8
#include "Common/Data/Encoding/Utf8.h"
9
#include "Common/File/FileUtil.h"
10
#include "Common/Data/Format/PNGLoad.h"
11
#include "ShellUtil.h"
12
13
#include <shlobj.h>
14
#include <commdlg.h>
15
#include <cderr.h>
16
17
namespace W32Util {
18
std::string BrowseForFolder(HWND parent, std::string_view title, std::string_view initialPath) {
19
std::wstring titleString = ConvertUTF8ToWString(title);
20
return BrowseForFolder(parent, titleString.c_str(), initialPath);
21
}
22
23
static int CALLBACK BrowseFolderCallback(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) {
24
if (uMsg == BFFM_INITIALIZED) {
25
LPCTSTR path = reinterpret_cast<LPCTSTR>(lpData);
26
::SendMessage(hwnd, BFFM_SETSELECTION, true, (LPARAM)path);
27
}
28
return 0;
29
}
30
31
std::string BrowseForFolder(HWND parent, const wchar_t *title, std::string_view initialPath) {
32
BROWSEINFO info{};
33
info.hwndOwner = parent;
34
info.lpszTitle = title;
35
info.ulFlags = BIF_EDITBOX | BIF_RETURNONLYFSDIRS | BIF_USENEWUI | BIF_NEWDIALOGSTYLE;
36
37
std::wstring initialPathW;
38
39
if (!initialPath.empty()) {
40
initialPathW = ConvertUTF8ToWString(initialPath);
41
info.lParam = reinterpret_cast<LPARAM>(initialPathW.c_str());
42
info.lpfn = BrowseFolderCallback;
43
}
44
45
//info.pszDisplayName
46
auto idList = SHBrowseForFolder(&info);
47
HMODULE shell32 = GetModuleHandle(L"shell32.dll");
48
typedef BOOL (WINAPI *SHGetPathFromIDListEx_f)(PCIDLIST_ABSOLUTE pidl, PWSTR pszPath, DWORD cchPath, GPFIDL_FLAGS uOpts);
49
SHGetPathFromIDListEx_f SHGetPathFromIDListEx_ = nullptr;
50
if (shell32)
51
SHGetPathFromIDListEx_ = (SHGetPathFromIDListEx_f)GetProcAddress(shell32, "SHGetPathFromIDListEx");
52
53
std::string result;
54
if (SHGetPathFromIDListEx_) {
55
std::wstring temp;
56
do {
57
// Assume it's failing if it goes on too long.
58
if (temp.size() > 32768 * 10) {
59
temp.clear();
60
break;
61
}
62
temp.resize(temp.size() + MAX_PATH);
63
} while (SHGetPathFromIDListEx_(idList, &temp[0], (DWORD)temp.size(), GPFIDL_DEFAULT) == 0);
64
result = ConvertWStringToUTF8(temp);
65
} else {
66
wchar_t temp[MAX_PATH]{};
67
SHGetPathFromIDList(idList, temp);
68
result = ConvertWStringToUTF8(temp);
69
}
70
71
CoTaskMemFree(idList);
72
return result;
73
}
74
75
bool BrowseForFileName(bool _bLoad, HWND _hParent, const wchar_t *_pTitle,
76
const wchar_t *_pInitialFolder, const wchar_t *_pFilter, const wchar_t *_pExtension,
77
std::string &_strFileName) {
78
// Let's hope this is large enough, don't want to trigger the dialog twice...
79
std::wstring filenameBuffer(32768 * 10, '\0');
80
81
OPENFILENAME ofn{ sizeof(OPENFILENAME) };
82
83
auto resetFileBuffer = [&] {
84
ofn.nMaxFile = (DWORD)filenameBuffer.size();
85
ofn.lpstrFile = &filenameBuffer[0];
86
if (!_strFileName.empty())
87
wcsncpy(ofn.lpstrFile, ConvertUTF8ToWString(_strFileName).c_str(), filenameBuffer.size() - 1);
88
};
89
90
resetFileBuffer();
91
ofn.lpstrInitialDir = _pInitialFolder;
92
ofn.lpstrFilter = _pFilter;
93
ofn.lpstrFileTitle = nullptr;
94
ofn.nMaxFileTitle = 0;
95
ofn.lpstrDefExt = _pExtension;
96
ofn.hwndOwner = _hParent;
97
ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER;
98
if (!_bLoad)
99
ofn.Flags |= OFN_HIDEREADONLY;
100
101
int success = _bLoad ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn);
102
if (success == 0 && CommDlgExtendedError() == FNERR_BUFFERTOOSMALL) {
103
size_t sz = *(unsigned short *)&filenameBuffer[0];
104
// Documentation is unclear if this is WCHARs to CHARs.
105
filenameBuffer.resize(filenameBuffer.size() + sz * 2);
106
resetFileBuffer();
107
success = _bLoad ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn);
108
}
109
110
if (success) {
111
_strFileName = ConvertWStringToUTF8(ofn.lpstrFile);
112
return true;
113
}
114
return false;
115
}
116
117
std::vector<std::string> BrowseForFileNameMultiSelect(bool _bLoad, HWND _hParent, const wchar_t *_pTitle,
118
const wchar_t *_pInitialFolder, const wchar_t *_pFilter, const wchar_t *_pExtension) {
119
// Let's hope this is large enough, don't want to trigger the dialog twice...
120
std::wstring filenameBuffer(32768 * 10, '\0');
121
122
OPENFILENAME ofn{ sizeof(OPENFILENAME) };
123
124
auto resetFileBuffer = [&] {
125
ofn.nMaxFile = (DWORD)filenameBuffer.size();
126
ofn.lpstrFile = &filenameBuffer[0];
127
};
128
129
resetFileBuffer();
130
ofn.lpstrInitialDir = _pInitialFolder;
131
ofn.lpstrFilter = _pFilter;
132
ofn.lpstrFileTitle = nullptr;
133
ofn.nMaxFileTitle = 0;
134
ofn.lpstrDefExt = _pExtension;
135
ofn.hwndOwner = _hParent;
136
ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER | OFN_ALLOWMULTISELECT;
137
if (!_bLoad)
138
ofn.Flags |= OFN_HIDEREADONLY;
139
140
std::vector<std::string> files;
141
int success = _bLoad ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn);
142
if (success == 0 && CommDlgExtendedError() == FNERR_BUFFERTOOSMALL) {
143
size_t sz = *(unsigned short *)&filenameBuffer[0];
144
// Documentation is unclear if this is WCHARs to CHARs.
145
filenameBuffer.resize(filenameBuffer.size() + sz * 2);
146
resetFileBuffer();
147
success = _bLoad ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn);
148
}
149
150
if (success) {
151
std::string directory = ConvertWStringToUTF8(ofn.lpstrFile);
152
wchar_t *temp = ofn.lpstrFile;
153
temp += wcslen(temp) + 1;
154
if (*temp == 0) {
155
//we only got one file
156
files.push_back(directory);
157
} else {
158
while (*temp) {
159
files.emplace_back(directory + "\\" + ConvertWStringToUTF8(temp));
160
temp += wcslen(temp) + 1;
161
}
162
}
163
}
164
return files;
165
}
166
167
std::string UserDocumentsPath() {
168
std::string result;
169
HMODULE shell32 = GetModuleHandle(L"shell32.dll");
170
typedef HRESULT(WINAPI *SHGetKnownFolderPath_f)(REFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath);
171
SHGetKnownFolderPath_f SHGetKnownFolderPath_ = nullptr;
172
if (shell32)
173
SHGetKnownFolderPath_ = (SHGetKnownFolderPath_f)GetProcAddress(shell32, "SHGetKnownFolderPath");
174
if (SHGetKnownFolderPath_) {
175
PWSTR path = nullptr;
176
if (SHGetKnownFolderPath_(FOLDERID_Documents, 0, nullptr, &path) == S_OK) {
177
result = ConvertWStringToUTF8(path);
178
}
179
if (path)
180
CoTaskMemFree(path);
181
} else {
182
wchar_t path[MAX_PATH];
183
if (SHGetFolderPath(nullptr, CSIDL_PERSONAL, nullptr, SHGFP_TYPE_CURRENT, path) == S_OK) {
184
result = ConvertWStringToUTF8(path);
185
}
186
}
187
188
return result;
189
}
190
191
192
// http://msdn.microsoft.com/en-us/library/aa969393.aspx
193
static HRESULT CreateLink(LPCWSTR lpszPathObj, LPCWSTR lpszArguments, LPCWSTR lpszPathLink, LPCWSTR lpszDesc, LPCWSTR lpszIcon, int iconIndex) {
194
HRESULT hres;
195
IShellLink *psl = nullptr;
196
hres = CoInitializeEx(NULL, COINIT_MULTITHREADED);
197
if (FAILED(hres))
198
return hres;
199
200
// Get a pointer to the IShellLink interface. It is assumed that CoInitialize
201
// has already been called.
202
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&psl);
203
if (SUCCEEDED(hres) && psl) {
204
IPersistFile *ppf = nullptr;
205
206
// Set the path to the shortcut target and add the description.
207
psl->SetPath(lpszPathObj);
208
psl->SetArguments(lpszArguments);
209
psl->SetDescription(lpszDesc);
210
if (lpszIcon) {
211
psl->SetIconLocation(lpszIcon, iconIndex);
212
}
213
214
// Query IShellLink for the IPersistFile interface, used for saving the
215
// shortcut in persistent storage.
216
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID *)&ppf);
217
218
if (SUCCEEDED(hres) && ppf) {
219
// Save the link by calling IPersistFile::Save.
220
hres = ppf->Save(lpszPathLink, TRUE);
221
ppf->Release();
222
}
223
psl->Release();
224
}
225
CoUninitialize();
226
227
return hres;
228
}
229
230
bool CreateDesktopShortcut(std::string_view argumentPath, std::string_view gameTitleStr, const Path &icoFile) {
231
// Get the desktop folder
232
wchar_t *pathbuf = new wchar_t[4096];
233
SHGetFolderPath(0, CSIDL_DESKTOPDIRECTORY, NULL, SHGFP_TYPE_CURRENT, pathbuf);
234
235
std::string gameTitle(gameTitleStr);
236
// Sanitize the game title for banned characters.
237
const char bannedChars[] = "<>:\"/\\|?*";
238
for (size_t i = 0; i < gameTitle.size(); i++) {
239
for (char c : bannedChars) {
240
if (gameTitle[i] == c) {
241
gameTitle[i] = '_';
242
break;
243
}
244
}
245
}
246
247
wcscat(pathbuf, L"\\");
248
wcscat(pathbuf, ConvertUTF8ToWString(gameTitle).c_str());
249
wcscat(pathbuf, L".lnk");
250
251
std::wstring moduleFilename;
252
size_t sz;
253
do {
254
moduleFilename.resize(moduleFilename.size() + MAX_PATH);
255
// On failure, this will return the same value as passed in, but success will always be one lower.
256
sz = GetModuleFileName(nullptr, &moduleFilename[0], (DWORD)moduleFilename.size());
257
} while (sz >= moduleFilename.size());
258
moduleFilename.resize(sz);
259
260
// Need to flip the slashes in the filename.
261
262
std::string sanitizedArgument(argumentPath);
263
for (size_t i = 0; i < sanitizedArgument.size(); i++) {
264
if (sanitizedArgument[i] == '/') {
265
sanitizedArgument[i] = '\\';
266
}
267
}
268
269
sanitizedArgument = "\"" + sanitizedArgument + "\"";
270
271
std::wstring icon;
272
if (!icoFile.empty()) {
273
icon = icoFile.ToWString();
274
}
275
276
CreateLink(moduleFilename.c_str(), ConvertUTF8ToWString(sanitizedArgument).c_str(), pathbuf, ConvertUTF8ToWString(gameTitle).c_str(), icon.empty() ? nullptr : icon.c_str(), 0);
277
278
// TODO: Also extract the game icon and convert to .ico, put it somewhere under Memstick, and set it.
279
280
delete[] pathbuf;
281
return false;
282
}
283
284
// Function to create an icon file from PNG image data (these icons require Windows Vista).
285
// The Old New Thing comes to the rescue again! ChatGPT failed miserably.
286
// https://devblogs.microsoft.com/oldnewthing/20101018-00/?p=12513
287
// https://devblogs.microsoft.com/oldnewthing/20101022-00/?p=12473
288
bool CreateICOFromPNGData(const uint8_t *imageData, size_t imageDataSize, const Path &icoPath) {
289
if (imageDataSize <= sizeof(PNGHeaderPeek)) {
290
return false;
291
}
292
// Parse the PNG
293
PNGHeaderPeek pngHeader;
294
memcpy(&pngHeader, imageData, sizeof(PNGHeaderPeek));
295
if (pngHeader.Width() > 256 || pngHeader.Height() > 256) {
296
// Reject the png as an icon.
297
return false;
298
}
299
300
struct IconHeader {
301
uint16_t reservedZero;
302
uint16_t type; // should be 1
303
uint16_t imageCount;
304
};
305
IconHeader hdr{ 0, 1, 1 };
306
struct IconDirectoryEntry {
307
BYTE bWidth;
308
BYTE bHeight;
309
BYTE bColorCount;
310
BYTE bReserved;
311
WORD wPlanes;
312
WORD wBitCount;
313
DWORD dwBytesInRes;
314
DWORD dwImageOffset;
315
};
316
IconDirectoryEntry entry{};
317
entry.bWidth = pngHeader.Width();
318
entry.bHeight = pngHeader.Height();
319
entry.bColorCount = 0;
320
entry.dwBytesInRes = (DWORD)imageDataSize;
321
entry.wPlanes = 1;
322
entry.wBitCount = 32;
323
entry.dwImageOffset = sizeof(hdr) + sizeof(entry);
324
325
FILE *file = File::OpenCFile(icoPath, "wb");
326
if (!file) {
327
return false;
328
}
329
fwrite(&hdr, sizeof(hdr), 1, file);
330
fwrite(&entry, sizeof(entry), 1, file);
331
fwrite(imageData, 1, imageDataSize, file);
332
fclose(file);
333
return true;
334
}
335
336
} // namespace
337
338