Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Windows/W32Util/ShellUtil.cpp
5669 views
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 <shobjidl.h> // For IFileDialog and related interfaces
14
#include <shellapi.h>
15
#include <shlobj.h>
16
#include <commdlg.h>
17
#include <cderr.h>
18
#include <wrl/client.h>
19
20
namespace W32Util {
21
using Microsoft::WRL::ComPtr;
22
23
void OpenDisplaySettings() {
24
// Windows 10/11 Settings URI
25
HINSTANCE res = ShellExecuteW(NULL, L"open", L"ms-settings:display-advanced", NULL, NULL, SW_SHOWNORMAL);
26
if ((INT_PTR)res <= 32) {
27
// Fallback for Windows 7/8
28
ShellExecuteW(NULL, L"open", L"desk.cpl", NULL, NULL, SW_SHOWNORMAL);
29
}
30
}
31
32
bool MoveToTrash(const Path &path) {
33
ComPtr<IFileOperation> pFileOp;
34
35
HRESULT hr = CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pFileOp));
36
if (FAILED(hr)) {
37
return false;
38
}
39
40
// Set operation flags
41
hr = pFileOp->SetOperationFlags(FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT);
42
if (FAILED(hr)) {
43
return false;
44
}
45
46
// Create a shell item from the file path
47
ComPtr<IShellItem> pItem;
48
hr = SHCreateItemFromParsingName(path.ToWString().c_str(), nullptr, IID_PPV_ARGS(&pItem));
49
if (SUCCEEDED(hr)) {
50
// Schedule the delete (move to recycle bin)
51
hr = pFileOp->DeleteItem(pItem.Get(), nullptr);
52
if (SUCCEEDED(hr)) {
53
hr = pFileOp->PerformOperations(); // Execute
54
}
55
}
56
57
if (SUCCEEDED(hr)) {
58
INFO_LOG(Log::IO, "Moved file to trash successfully: %s", path.c_str());
59
return true;
60
} else {
61
WARN_LOG(Log::IO, "Failed to move file to trash: %s", path.c_str());
62
return false;
63
}
64
}
65
66
std::string BrowseForFolder2(HWND parent, std::string_view title, std::string_view initialPath) {
67
const std::wstring wtitle = ConvertUTF8ToWString(title);
68
const std::wstring initialDir = ConvertUTF8ToWString(initialPath);
69
70
std::wstring selectedFolder;
71
72
// Create the FileOpenDialog object
73
ComPtr<IFileDialog> pFileDialog;
74
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFileDialog));
75
if (!SUCCEEDED(hr)) {
76
return "";
77
}
78
// Set the options to select folders instead of files
79
DWORD dwOptions;
80
hr = pFileDialog->GetOptions(&dwOptions);
81
if (SUCCEEDED(hr)) {
82
hr = pFileDialog->SetOptions(dwOptions | FOS_PICKFOLDERS);
83
} else {
84
return "";
85
}
86
87
// Set the initial directory
88
if (!initialDir.empty()) {
89
ComPtr<IShellItem> pShellItem;
90
hr = SHCreateItemFromParsingName(initialDir.c_str(), nullptr, IID_PPV_ARGS(&pShellItem));
91
if (SUCCEEDED(hr)) {
92
hr = pFileDialog->SetFolder(pShellItem.Get());
93
}
94
}
95
pFileDialog->SetTitle(wtitle.c_str());
96
97
// Show the dialog
98
hr = pFileDialog->Show(parent);
99
if (SUCCEEDED(hr)) {
100
// Get the selected folder
101
ComPtr<IShellItem> pShellItem;
102
hr = pFileDialog->GetResult(&pShellItem);
103
if (SUCCEEDED(hr)) {
104
PWSTR pszFilePath = nullptr;
105
hr = pShellItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
106
if (SUCCEEDED(hr)) {
107
selectedFolder = pszFilePath;
108
CoTaskMemFree(pszFilePath);
109
}
110
}
111
}
112
113
return ConvertWStringToUTF8(selectedFolder);
114
}
115
116
bool BrowseForFileName(bool _bLoad, HWND _hParent, const wchar_t *_pTitle,
117
const wchar_t *_pInitialFolder, const wchar_t *_pFilter, const wchar_t *_pExtension,
118
std::string &_strFileName) {
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
if (!_strFileName.empty())
128
wcsncpy(ofn.lpstrFile, ConvertUTF8ToWString(_strFileName).c_str(), filenameBuffer.size() - 1);
129
};
130
131
resetFileBuffer();
132
ofn.lpstrInitialDir = _pInitialFolder;
133
ofn.lpstrFilter = _pFilter;
134
ofn.lpstrFileTitle = nullptr;
135
ofn.nMaxFileTitle = 0;
136
ofn.lpstrDefExt = _pExtension;
137
ofn.hwndOwner = _hParent;
138
ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER;
139
if (!_bLoad)
140
ofn.Flags |= OFN_HIDEREADONLY;
141
142
int success = _bLoad ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn);
143
if (success == 0 && CommDlgExtendedError() == FNERR_BUFFERTOOSMALL) {
144
size_t sz = *(unsigned short *)&filenameBuffer[0];
145
// Documentation is unclear if this is WCHARs to CHARs.
146
filenameBuffer.resize(filenameBuffer.size() + sz * 2);
147
resetFileBuffer();
148
success = _bLoad ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn);
149
}
150
151
if (success) {
152
_strFileName = ConvertWStringToUTF8(ofn.lpstrFile);
153
return true;
154
}
155
return false;
156
}
157
158
std::vector<std::string> BrowseForFileNameMultiSelect(bool _bLoad, HWND _hParent, const wchar_t *_pTitle,
159
const wchar_t *_pInitialFolder, const wchar_t *_pFilter, const wchar_t *_pExtension) {
160
// Let's hope this is large enough, don't want to trigger the dialog twice...
161
std::wstring filenameBuffer(32768 * 10, '\0');
162
163
OPENFILENAME ofn{ sizeof(OPENFILENAME) };
164
165
auto resetFileBuffer = [&] {
166
ofn.nMaxFile = (DWORD)filenameBuffer.size();
167
ofn.lpstrFile = &filenameBuffer[0];
168
};
169
170
resetFileBuffer();
171
ofn.lpstrInitialDir = _pInitialFolder;
172
ofn.lpstrFilter = _pFilter;
173
ofn.lpstrFileTitle = nullptr;
174
ofn.nMaxFileTitle = 0;
175
ofn.lpstrDefExt = _pExtension;
176
ofn.hwndOwner = _hParent;
177
ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER | OFN_ALLOWMULTISELECT;
178
if (!_bLoad)
179
ofn.Flags |= OFN_HIDEREADONLY;
180
181
std::vector<std::string> files;
182
int success = _bLoad ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn);
183
if (success == 0 && CommDlgExtendedError() == FNERR_BUFFERTOOSMALL) {
184
size_t sz = *(unsigned short *)&filenameBuffer[0];
185
// Documentation is unclear if this is WCHARs to CHARs.
186
filenameBuffer.resize(filenameBuffer.size() + sz * 2);
187
resetFileBuffer();
188
success = _bLoad ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn);
189
}
190
191
if (success) {
192
std::string directory = ConvertWStringToUTF8(ofn.lpstrFile);
193
wchar_t *temp = ofn.lpstrFile;
194
temp += wcslen(temp) + 1;
195
if (*temp == 0) {
196
//we only got one file
197
files.push_back(directory);
198
} else {
199
while (*temp) {
200
files.emplace_back(directory + "\\" + ConvertWStringToUTF8(temp));
201
temp += wcslen(temp) + 1;
202
}
203
}
204
}
205
return files;
206
}
207
208
std::string UserDocumentsPath() {
209
std::string result;
210
HMODULE shell32 = GetModuleHandle(L"shell32.dll");
211
typedef HRESULT(WINAPI *SHGetKnownFolderPath_f)(REFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath);
212
SHGetKnownFolderPath_f SHGetKnownFolderPath_ = nullptr;
213
if (shell32)
214
SHGetKnownFolderPath_ = (SHGetKnownFolderPath_f)GetProcAddress(shell32, "SHGetKnownFolderPath");
215
if (SHGetKnownFolderPath_) {
216
PWSTR path = nullptr;
217
if (SHGetKnownFolderPath_(FOLDERID_Documents, 0, nullptr, &path) == S_OK) {
218
result = ConvertWStringToUTF8(path);
219
}
220
if (path)
221
CoTaskMemFree(path);
222
} else {
223
wchar_t path[MAX_PATH];
224
if (SHGetFolderPath(nullptr, CSIDL_PERSONAL, nullptr, SHGFP_TYPE_CURRENT, path) == S_OK) {
225
result = ConvertWStringToUTF8(path);
226
}
227
}
228
229
return result;
230
}
231
232
233
// http://msdn.microsoft.com/en-us/library/aa969393.aspx
234
static HRESULT CreateLink(LPCWSTR lpszPathObj, LPCWSTR lpszArguments, LPCWSTR lpszPathLink, LPCWSTR lpszDesc, LPCWSTR lpszIcon, int iconIndex) {
235
HRESULT hres;
236
ComPtr<IShellLink> psl;
237
hres = CoInitializeEx(NULL, COINIT_MULTITHREADED);
238
if (FAILED(hres))
239
return hres;
240
241
// Get a pointer to the IShellLink interface. It is assumed that CoInitialize
242
// has already been called.
243
hres = CoCreateInstance(__uuidof(ShellLink), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&psl));
244
if (SUCCEEDED(hres) && psl) {
245
ComPtr<IPersistFile> ppf;
246
247
// Set the path to the shortcut target and add the description.
248
psl->SetPath(lpszPathObj);
249
psl->SetArguments(lpszArguments);
250
psl->SetDescription(lpszDesc);
251
if (lpszIcon) {
252
psl->SetIconLocation(lpszIcon, iconIndex);
253
}
254
255
// Query IShellLink for the IPersistFile interface, used for saving the
256
// shortcut in persistent storage.
257
hres = psl.As(&ppf);
258
259
if (SUCCEEDED(hres) && ppf) {
260
// Save the link by calling IPersistFile::Save.
261
hres = ppf->Save(lpszPathLink, TRUE);
262
}
263
}
264
CoUninitialize();
265
266
return hres;
267
}
268
269
bool CreateDesktopShortcut(std::string_view argumentPath, std::string_view gameTitleStr, const Path &icoFile) {
270
// Get the desktop folder
271
wchar_t *pathbuf = new wchar_t[4096];
272
SHGetFolderPath(0, CSIDL_DESKTOPDIRECTORY, NULL, SHGFP_TYPE_CURRENT, pathbuf);
273
274
std::string gameTitle(gameTitleStr);
275
// Sanitize the game title for banned characters.
276
const char bannedChars[] = "<>:\"/\\|?*";
277
for (size_t i = 0; i < gameTitle.size(); i++) {
278
for (char c : bannedChars) {
279
if (gameTitle[i] == c) {
280
gameTitle[i] = '_';
281
break;
282
}
283
}
284
}
285
286
wcscat(pathbuf, L"\\");
287
wcscat(pathbuf, ConvertUTF8ToWString(gameTitle).c_str());
288
wcscat(pathbuf, L".lnk");
289
290
std::wstring moduleFilename;
291
size_t sz;
292
do {
293
moduleFilename.resize(moduleFilename.size() + MAX_PATH);
294
// On failure, this will return the same value as passed in, but success will always be one lower.
295
sz = GetModuleFileName(nullptr, &moduleFilename[0], (DWORD)moduleFilename.size());
296
} while (sz >= moduleFilename.size());
297
moduleFilename.resize(sz);
298
299
// Need to flip the slashes in the filename.
300
301
std::string sanitizedArgument(argumentPath);
302
for (size_t i = 0; i < sanitizedArgument.size(); i++) {
303
if (sanitizedArgument[i] == '/') {
304
sanitizedArgument[i] = '\\';
305
}
306
}
307
308
sanitizedArgument = "\"" + sanitizedArgument + "\"";
309
310
std::wstring icon;
311
if (!icoFile.empty()) {
312
icon = icoFile.ToWString();
313
}
314
315
CreateLink(moduleFilename.c_str(), ConvertUTF8ToWString(sanitizedArgument).c_str(), pathbuf, ConvertUTF8ToWString(gameTitle).c_str(), icon.empty() ? nullptr : icon.c_str(), 0);
316
317
// TODO: Also extract the game icon and convert to .ico, put it somewhere under Memstick, and set it.
318
319
delete[] pathbuf;
320
return false;
321
}
322
323
// Function to create an icon file from PNG image data (these icons require Windows Vista).
324
// The Old New Thing comes to the rescue again! ChatGPT failed miserably.
325
// https://devblogs.microsoft.com/oldnewthing/20101018-00/?p=12513
326
// https://devblogs.microsoft.com/oldnewthing/20101022-00/?p=12473
327
bool CreateICOFromPNGData(const uint8_t *imageData, size_t imageDataSize, const Path &icoPath) {
328
if (imageDataSize <= sizeof(PNGHeaderPeek)) {
329
return false;
330
}
331
// Parse the PNG
332
PNGHeaderPeek pngHeader;
333
memcpy(&pngHeader, imageData, sizeof(PNGHeaderPeek));
334
if (pngHeader.Width() > 256 || pngHeader.Height() > 256) {
335
// Reject the png as an icon.
336
return false;
337
}
338
339
struct IconHeader {
340
uint16_t reservedZero;
341
uint16_t type; // should be 1
342
uint16_t imageCount;
343
};
344
IconHeader hdr{ 0, 1, 1 };
345
struct IconDirectoryEntry {
346
BYTE bWidth;
347
BYTE bHeight;
348
BYTE bColorCount;
349
BYTE bReserved;
350
WORD wPlanes;
351
WORD wBitCount;
352
DWORD dwBytesInRes;
353
DWORD dwImageOffset;
354
};
355
IconDirectoryEntry entry{};
356
entry.bWidth = pngHeader.Width();
357
entry.bHeight = pngHeader.Height();
358
entry.bColorCount = 0;
359
entry.dwBytesInRes = (DWORD)imageDataSize;
360
entry.wPlanes = 1;
361
entry.wBitCount = 32;
362
entry.dwImageOffset = sizeof(hdr) + sizeof(entry);
363
364
FILE *file = File::OpenCFile(icoPath, "wb");
365
if (!file) {
366
return false;
367
}
368
fwrite(&hdr, sizeof(hdr), 1, file);
369
fwrite(&entry, sizeof(entry), 1, file);
370
fwrite(imageData, 1, imageDataSize, file);
371
fclose(file);
372
return true;
373
}
374
375
} // namespace W32Util
376
377