Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Windows/W32Util/Misc.cpp
5656 views
1
#include "ppsspp_config.h"
2
#include "Common/CommonWindows.h"
3
4
#include <WinUser.h>
5
#include <shellapi.h>
6
#include <commctrl.h>
7
#include <ShlObj.h>
8
9
#include "Misc.h"
10
#include "Common/Data/Encoding/Utf8.h"
11
#include "Common/StringUtils.h"
12
#include "Common/File/FileUtil.h"
13
#include "Common/Log.h"
14
15
bool KeyDownAsync(int vkey) {
16
#if PPSSPP_PLATFORM(UWP)
17
return 0;
18
#else
19
return (GetAsyncKeyState(vkey) & 0x8000) != 0;
20
#endif
21
}
22
23
namespace W32Util
24
{
25
static RECT MapRectFromClientToWndCoords(HWND hwnd, const RECT &r) {
26
RECT wnd_coords = r;
27
// map to screen
28
MapWindowPoints(hwnd, NULL, reinterpret_cast<POINT *>(&wnd_coords), 2);
29
RECT scr_coords;
30
GetWindowRect(hwnd, &scr_coords);
31
// map to window coords by substracting the window coord origin in screen coords.
32
OffsetRect(&wnd_coords, -scr_coords.left, -scr_coords.top);
33
return wnd_coords;
34
}
35
36
RECT GetNonclientMenuBorderRect(HWND hwnd) {
37
RECT r;
38
GetClientRect(hwnd, &r);
39
r = MapRectFromClientToWndCoords(hwnd, r);
40
const int y = r.top - 1;
41
return {
42
r.left,
43
y,
44
r.right,
45
y + 1
46
};
47
}
48
49
void CenterWindow(HWND hwnd) {
50
HWND hwndParent;
51
RECT rect, rectP;
52
int width, height;
53
int screenwidth, screenheight;
54
int x, y;
55
56
//make the window relative to its parent
57
hwndParent = GetParent(hwnd);
58
if (!hwndParent)
59
return;
60
61
GetWindowRect(hwnd, &rect);
62
GetWindowRect(hwndParent, &rectP);
63
64
width = rect.right - rect.left;
65
height = rect.bottom - rect.top;
66
67
x = ((rectP.right-rectP.left) - width) / 2 + rectP.left;
68
y = ((rectP.bottom-rectP.top) - height) / 2 + rectP.top;
69
70
screenwidth = GetSystemMetrics(SM_CXSCREEN);
71
screenheight = GetSystemMetrics(SM_CYSCREEN);
72
73
//make sure that the dialog box never moves outside of
74
//the screen
75
if(x < 0) x = 0;
76
if(y < 0) y = 0;
77
if(x + width > screenwidth) x = screenwidth - width;
78
if(y + height > screenheight) y = screenheight - height;
79
80
MoveWindow(hwnd, x, y, width, height, FALSE);
81
}
82
83
bool CopyTextToClipboard(HWND hWnd, std::string_view text) {
84
std::wstring wtext = ConvertUTF8ToWString(text);
85
if (!OpenClipboard(hWnd)) {
86
return false;
87
}
88
89
EmptyClipboard();
90
HANDLE hglbCopy = GlobalAlloc(GMEM_MOVEABLE, (wtext.size() + 1) * sizeof(wchar_t));
91
if (!hglbCopy) {
92
CloseClipboard();
93
return false;
94
}
95
96
// Lock the handle and copy the text to the buffer.
97
98
wchar_t *lptstrCopy = (wchar_t *)GlobalLock(hglbCopy);
99
if (lptstrCopy) {
100
wcscpy(lptstrCopy, wtext.c_str());
101
lptstrCopy[wtext.size()] = (wchar_t) 0; // null character
102
GlobalUnlock(hglbCopy);
103
SetClipboardData(CF_UNICODETEXT, hglbCopy);
104
}
105
CloseClipboard();
106
return lptstrCopy != nullptr;
107
}
108
109
void MakeTopMost(HWND hwnd, bool topMost) {
110
SetWindowPos(hwnd, topMost ? HWND_TOPMOST : HWND_NOTOPMOST, 0,0,0,0, SWP_NOMOVE | SWP_NOSIZE);
111
}
112
113
void GetWindowRes(HWND hWnd, int *xres, int *yres) {
114
RECT rc;
115
GetClientRect(hWnd, &rc);
116
*xres = rc.right - rc.left;
117
*yres = rc.bottom - rc.top;
118
}
119
120
void ShowFileInFolder(std::string_view path) {
121
// SHParseDisplayName can't handle relative paths, so normalize first.
122
std::string resolved = ReplaceAll(File::ResolvePath(path), "/", "\\");
123
124
// Shell also can't handle \\?\UNC\ paths.
125
// TODO: Move this to ResolvePath?
126
if (startsWith(resolved, "\\\\?\\UNC\\")) {
127
resolved = "\\" + resolved.substr(7);
128
}
129
130
PIDLIST_ABSOLUTE pidl = nullptr;
131
std::wstring wresolved = ConvertUTF8ToWString(resolved);
132
HRESULT hr = SHParseDisplayName(wresolved.c_str(), nullptr, &pidl, 0, nullptr);
133
134
if (pidl) {
135
if (SUCCEEDED(hr))
136
SHOpenFolderAndSelectItems(pidl, 0, nullptr, 0);
137
ILFree(pidl);
138
} else {
139
ERROR_LOG(Log::System, "SHParseDisplayName failed: %s", resolved.c_str());
140
}
141
}
142
143
static const wchar_t *RemoveExecutableFromCommandLine(const wchar_t *cmdline) {
144
if (!cmdline) {
145
return L"";
146
}
147
148
switch (cmdline[0]) {
149
case '"':
150
// We don't need to handle escaped quotes, since filenames can't have that.
151
cmdline = wcschr(cmdline + 1, '"');
152
if (cmdline) {
153
++cmdline;
154
if (cmdline[0] == ' ') {
155
++cmdline;
156
}
157
}
158
break;
159
160
default:
161
cmdline = wcschr(cmdline, ' ');
162
if (cmdline) {
163
++cmdline;
164
}
165
break;
166
}
167
168
return cmdline;
169
}
170
171
void GetSelfExecuteParams(std::wstring &workingDirectory, std::wstring &moduleFilename) {
172
workingDirectory.resize(MAX_PATH);
173
size_t sz = GetCurrentDirectoryW((DWORD)workingDirectory.size(), &workingDirectory[0]);
174
if (sz != 0 && sz < workingDirectory.size()) {
175
// This means success, so now we can remove the null terminator.
176
workingDirectory.resize(sz);
177
} else if (sz > workingDirectory.size()) {
178
// If insufficient, sz will include the null terminator, so we remove after.
179
workingDirectory.resize(sz);
180
sz = GetCurrentDirectoryW((DWORD)sz, &workingDirectory[0]);
181
workingDirectory.resize(sz);
182
}
183
184
moduleFilename.clear();
185
do {
186
moduleFilename.resize(moduleFilename.size() + MAX_PATH);
187
// On failure, this will return the same value as passed in, but success will always be one lower.
188
sz = GetModuleFileName(GetModuleHandle(nullptr), &moduleFilename[0], (DWORD)moduleFilename.size());
189
} while (sz >= moduleFilename.size());
190
moduleFilename.resize(sz);
191
}
192
193
void ExitAndRestart(bool overrideArgs, const std::string &args) {
194
SpawnNewInstance(overrideArgs, args);
195
196
ExitProcess(0);
197
}
198
199
bool ExecuteAndGetReturnCode(const wchar_t *executable, const wchar_t *cmdline, const wchar_t *currentDirectory, DWORD *exitCode) {
200
PROCESS_INFORMATION processInformation = { 0 };
201
STARTUPINFO startupInfo = { 0 };
202
startupInfo.cb = sizeof(startupInfo);
203
204
std::wstring cmdlineW;
205
cmdlineW += L"PPSSPP "; // could also put the executable name as first argument, but concerned about escaping.
206
cmdlineW += cmdline;
207
208
// Create the process
209
bool result = CreateProcess(executable, (LPWSTR)cmdlineW.c_str(),
210
NULL, NULL, FALSE,
211
NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW,
212
NULL, currentDirectory, &startupInfo, &processInformation);
213
214
if (!result) {
215
// We failed.
216
return false;
217
}
218
219
// Successfully created the process. Wait for it to finish.
220
WaitForSingleObject(processInformation.hProcess, INFINITE);
221
result = GetExitCodeProcess(processInformation.hProcess, exitCode);
222
CloseHandle(processInformation.hProcess);
223
CloseHandle(processInformation.hThread);
224
return result != 0;
225
}
226
227
void SpawnNewInstance(bool overrideArgs, const std::string &args) {
228
// This preserves arguments (for example, config file) and working directory.
229
std::wstring workingDirectory;
230
std::wstring moduleFilename;
231
GetSelfExecuteParams(workingDirectory, moduleFilename);
232
233
const wchar_t *cmdline;
234
std::wstring wargs;
235
if (overrideArgs) {
236
wargs = ConvertUTF8ToWString(args);
237
cmdline = wargs.c_str();
238
} else {
239
cmdline = RemoveExecutableFromCommandLine(GetCommandLineW());
240
}
241
ShellExecute(nullptr, nullptr, moduleFilename.c_str(), cmdline, workingDirectory.c_str(), SW_SHOW);
242
}
243
244
ClipboardData::ClipboardData(const char *format, size_t sz) {
245
format_ = RegisterClipboardFormatA(format);
246
handle_ = format_ != 0 ? GlobalAlloc(GHND, sz) : 0;
247
data = handle_ != 0 ? GlobalLock(handle_) : nullptr;
248
}
249
250
ClipboardData::ClipboardData(UINT format, size_t sz) {
251
format_ = format;
252
handle_ = GlobalAlloc(GHND, sz);
253
data = handle_ != 0 ? GlobalLock(handle_) : nullptr;
254
}
255
256
ClipboardData::~ClipboardData() {
257
if (handle_ != 0) {
258
GlobalUnlock(handle_);
259
GlobalFree(handle_);
260
}
261
}
262
263
void ClipboardData::Set() {
264
if (format_ == 0 || handle_ == 0 || data == 0)
265
return;
266
SetClipboardData(format_, handle_);
267
}
268
}
269
270
static constexpr UINT_PTR IDT_UPDATE = 0xC0DE0042;
271
static constexpr UINT UPDATE_DELAY = 1000 / 60;
272
273
GenericListControl::GenericListControl(HWND hwnd, const GenericListViewDef& def)
274
: handle(hwnd), columns(def.columns),columnCount(def.columnCount),valid(false),
275
inResizeColumns(false),updating(false)
276
{
277
DWORD style = GetWindowLong(handle,GWL_STYLE) | LVS_REPORT;
278
SetWindowLong(handle, GWL_STYLE, style);
279
280
SetWindowLongPtr(handle,GWLP_USERDATA,(LONG_PTR)this);
281
oldProc = (WNDPROC) SetWindowLongPtr(handle,GWLP_WNDPROC,(LONG_PTR)wndProc);
282
283
auto exStyle = LVS_EX_FULLROWSELECT;
284
if (def.checkbox)
285
exStyle |= LVS_EX_CHECKBOXES;
286
SendMessage(handle, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, exStyle);
287
288
LVCOLUMN lvc;
289
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
290
lvc.iSubItem = 0;
291
292
RECT rect;
293
GetClientRect(handle,&rect);
294
295
int totalListSize = rect.right-rect.left;
296
for (int i = 0; i < columnCount; i++) {
297
lvc.cx = (int)(columns[i].size * totalListSize);
298
lvc.pszText = (LPTSTR)columns[i].name;
299
300
if (columns[i].flags & GLVC_CENTERED)
301
lvc.fmt = LVCFMT_CENTER;
302
else
303
lvc.fmt = LVCFMT_LEFT;
304
305
ListView_InsertColumn(handle, i, &lvc);
306
}
307
308
if (def.columnOrder != NULL)
309
ListView_SetColumnOrderArray(handle,columnCount,def.columnOrder);
310
311
SetSendInvalidRows(false);
312
valid = true;
313
}
314
315
GenericListControl::~GenericListControl() {
316
SetWindowLongPtr(handle, GWLP_USERDATA, (LONG_PTR)nullptr);
317
// Don't destroy the image list, it's done automatically by the list view.
318
}
319
320
void GenericListControl::SetIconList(int w, int h, const std::vector<HICON> &icons) {
321
images_ = ImageList_Create(w, h, ILC_COLOR32 | ILC_MASK, 0, (int)icons.size());
322
for (const HICON &icon : icons)
323
ImageList_AddIcon((HIMAGELIST)images_, icon);
324
325
ListView_SetImageList(handle, (HIMAGELIST)images_, LVSIL_STATE);
326
}
327
328
int GenericListControl::HandleNotify(LPARAM lParam) {
329
LPNMHDR mhdr = (LPNMHDR) lParam;
330
331
if (mhdr->code == NM_DBLCLK)
332
{
333
LPNMITEMACTIVATE item = (LPNMITEMACTIVATE) lParam;
334
if ((item->iItem != -1 && item->iItem < GetRowCount()) || sendInvalidRows)
335
OnDoubleClick(item->iItem,item->iSubItem);
336
return 0;
337
}
338
339
if (mhdr->code == NM_RCLICK)
340
{
341
const LPNMITEMACTIVATE item = (LPNMITEMACTIVATE)lParam;
342
if ((item->iItem != -1 && item->iItem < GetRowCount()) || sendInvalidRows)
343
OnRightClick(item->iItem,item->iSubItem,item->ptAction);
344
return 0;
345
}
346
347
if (mhdr->code == NM_CUSTOMDRAW && (ListenRowPrePaint() || ListenColPrePaint())) {
348
LPNMLVCUSTOMDRAW msg = (LPNMLVCUSTOMDRAW)lParam;
349
switch (msg->nmcd.dwDrawStage) {
350
case CDDS_PREPAINT:
351
return CDRF_NOTIFYITEMDRAW;
352
353
case CDDS_ITEMPREPAINT:
354
if (OnRowPrePaint((int)msg->nmcd.dwItemSpec, msg)) {
355
return CDRF_NEWFONT;
356
}
357
return ListenColPrePaint() ? CDRF_NOTIFYSUBITEMDRAW : CDRF_DODEFAULT;
358
359
case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
360
if (OnColPrePaint((int)msg->nmcd.dwItemSpec, msg->iSubItem, msg)) {
361
return CDRF_NEWFONT;
362
}
363
return CDRF_DODEFAULT;
364
}
365
366
return CDRF_DODEFAULT;
367
}
368
369
if (mhdr->code == LVN_GETDISPINFO)
370
{
371
NMLVDISPINFO* dispInfo = (NMLVDISPINFO*)lParam;
372
373
stringBuffer[0] = 0;
374
GetColumnText(stringBuffer, ARRAY_SIZE(stringBuffer), dispInfo->item.iItem,dispInfo->item.iSubItem);
375
376
if (stringBuffer[0] == 0)
377
wcscat(stringBuffer,L"Invalid");
378
379
dispInfo->item.pszText = stringBuffer;
380
dispInfo->item.mask |= LVIF_TEXT;
381
return 0;
382
}
383
384
// handle checkboxes
385
if (mhdr->code == LVN_ITEMCHANGED && updating == false)
386
{
387
NMLISTVIEW* item = (NMLISTVIEW*) lParam;
388
if (item->iItem != -1 && (item->uChanged & LVIF_STATE) != 0)
389
{
390
// image is 1 if unchcked, 2 if checked
391
int oldImage = (item->uOldState & LVIS_STATEIMAGEMASK) >> 12;
392
int newImage = (item->uNewState & LVIS_STATEIMAGEMASK) >> 12;
393
if (oldImage != newImage)
394
OnToggle(item->iItem,newImage == 2);
395
}
396
397
return 0;
398
}
399
400
if (mhdr->code == LVN_INCREMENTALSEARCH) {
401
NMLVFINDITEM *request = (NMLVFINDITEM *)lParam;
402
uint32_t supported = LVFI_WRAP | LVFI_STRING | LVFI_PARTIAL | LVFI_SUBSTRING;
403
if ((request->lvfi.flags & ~supported) == 0 && (request->lvfi.flags & LVFI_STRING) != 0) {
404
bool wrap = (request->lvfi.flags & LVFI_WRAP) != 0;
405
bool partial = (request->lvfi.flags & (LVFI_PARTIAL | LVFI_SUBSTRING)) != 0;
406
407
// It seems like 0 is always sent for start, let's override.
408
int startRow = request->iStart;
409
if (startRow == 0)
410
startRow = GetSelectedIndex();
411
int result = OnIncrementalSearch(startRow, request->lvfi.psz, wrap, partial);
412
if (result != -1) {
413
request->lvfi.flags = LVFI_PARAM;
414
request->lvfi.lParam = (LPARAM)result;
415
}
416
}
417
}
418
419
return 0;
420
}
421
422
int GenericListControl::OnIncrementalSearch(int startRow, const wchar_t *str, bool wrap, bool partial) {
423
int size = GetRowCount();
424
size_t searchlen = wcslen(str);
425
if (!wrap)
426
size -= startRow;
427
428
// We start with the earliest column, preferring matches on the leftmost columns by default.
429
for (int c = 0; c < columnCount; ++c) {
430
for (int i = 0; i < size; ++i) {
431
int r = (startRow + i) % size;
432
stringBuffer[0] = 0;
433
GetColumnText(stringBuffer, ARRAY_SIZE(stringBuffer), r, c);
434
int difference = partial ? _wcsnicmp(str, stringBuffer, searchlen) : _wcsicmp(str, stringBuffer);
435
if (difference == 0)
436
return r;
437
}
438
}
439
440
return -1;
441
}
442
443
void GenericListControl::Update() {
444
if (!updateScheduled_) {
445
SetTimer(handle, IDT_UPDATE, UPDATE_DELAY, nullptr);
446
updateScheduled_ = true;
447
}
448
}
449
450
void GenericListControl::ProcessUpdate() {
451
updating = true;
452
int newRows = GetRowCount();
453
454
int items = ListView_GetItemCount(handle);
455
ListView_SetItemCount(handle, newRows);
456
457
// Scroll to top if we're removing items. It kinda does this automatically, but it's buggy.
458
if (items > newRows) {
459
POINT pt{};
460
ListView_GetOrigin(handle, &pt);
461
462
if (pt.x != 0 || pt.y != 0)
463
ListView_Scroll(handle, -pt.x, -pt.y);
464
}
465
466
while (items < newRows)
467
{
468
LVITEM lvI;
469
lvI.pszText = LPSTR_TEXTCALLBACK; // Sends an LVN_GETDISPINFO message.
470
lvI.mask = LVIF_TEXT | LVIF_IMAGE |LVIF_STATE;
471
lvI.stateMask = 0;
472
lvI.iSubItem = 0;
473
lvI.state = 0;
474
lvI.iItem = items;
475
lvI.iImage = items;
476
477
ListView_InsertItem(handle, &lvI);
478
items++;
479
}
480
481
while (items > newRows)
482
{
483
ListView_DeleteItem(handle,--items);
484
}
485
486
for (auto &act : pendingActions_) {
487
switch (act.action) {
488
case Action::CHECK:
489
ListView_SetCheckState(handle, act.item, act.state ? TRUE : FALSE);
490
break;
491
492
case Action::IMAGE:
493
ListView_SetItemState(handle, act.item, (act.state & 0xF) << 12, LVIS_STATEIMAGEMASK);
494
break;
495
}
496
}
497
pendingActions_.clear();
498
499
ResizeColumns();
500
501
InvalidateRect(handle, nullptr, TRUE);
502
ListView_RedrawItems(handle, 0, newRows - 1);
503
UpdateWindow(handle);
504
updating = false;
505
}
506
507
508
void GenericListControl::SetCheckState(int item, bool state) {
509
pendingActions_.push_back({ Action::CHECK, item, state ? 1 : 0 });
510
Update();
511
}
512
513
void GenericListControl::SetItemState(int item, uint8_t state) {
514
pendingActions_.push_back({ Action::IMAGE, item, (int)state });
515
Update();
516
}
517
518
void GenericListControl::ResizeColumns()
519
{
520
if (inResizeColumns)
521
return;
522
inResizeColumns = true;
523
524
RECT rect;
525
GetClientRect(handle, &rect);
526
527
int totalListSize = rect.right - rect.left;
528
for (int i = 0; i < columnCount; i++)
529
{
530
ListView_SetColumnWidth(handle, i, columns[i].size * totalListSize);
531
}
532
inResizeColumns = false;
533
}
534
535
LRESULT CALLBACK GenericListControl::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
536
{
537
GenericListControl* list = (GenericListControl*) GetWindowLongPtr(hwnd,GWLP_USERDATA);
538
if (!list)
539
return FALSE;
540
541
LRESULT returnValue;
542
if (list->valid && list->WindowMessage(msg,wParam,lParam,returnValue) == true)
543
return returnValue;
544
545
switch (msg)
546
{
547
case WM_SIZE:
548
list->ResizeColumns();
549
break;
550
551
case WM_KEYDOWN:
552
switch (wParam)
553
{
554
case VK_INSERT:
555
case 'C':
556
if (KeyDownAsync(VK_CONTROL))
557
list->ProcessCopy();
558
break;
559
560
case 'A':
561
if (KeyDownAsync(VK_CONTROL))
562
list->SelectAll();
563
break;
564
}
565
break;
566
567
case WM_TIMER:
568
if (wParam == IDT_UPDATE) {
569
list->ProcessUpdate();
570
list->updateScheduled_ = false;
571
KillTimer(hwnd, wParam);
572
}
573
break;
574
}
575
576
return (LRESULT)CallWindowProc((WNDPROC)list->oldProc,hwnd,msg,wParam,lParam);
577
}
578
579
void GenericListControl::ProcessCopy() {
580
int start = GetSelectedIndex();
581
int size;
582
if (start == -1)
583
size = GetRowCount();
584
else
585
size = ListView_GetSelectedCount(handle);
586
587
CopyRows(start, size);
588
}
589
590
void GenericListControl::CopyRows(int start, int size) {
591
std::wstring data;
592
593
if (start == 0 && size == GetRowCount()) {
594
// Let's also copy the header if everything is selected.
595
for (int c = 0; c < columnCount; ++c) {
596
data.append(columns[c].name);
597
if (c < columnCount - 1)
598
data.append(L"\t");
599
else
600
data.append(L"\r\n");
601
}
602
}
603
604
for (int r = start; r < start + size; ++r) {
605
for (int c = 0; c < columnCount; ++c) {
606
stringBuffer[0] = 0;
607
GetColumnText(stringBuffer, ARRAY_SIZE(stringBuffer), r, c);
608
data.append(stringBuffer);
609
if (c < columnCount - 1)
610
data.append(L"\t");
611
else
612
data.append(L"\r\n");
613
}
614
}
615
616
W32Util::CopyTextToClipboard(handle, ConvertWStringToUTF8(data));
617
}
618
619
void GenericListControl::SelectAll() {
620
ListView_SetItemState(handle, -1, LVIS_SELECTED, LVIS_SELECTED);
621
}
622
623
int GenericListControl::GetSelectedIndex() {
624
return ListView_GetNextItem(handle, -1, LVNI_SELECTED);
625
}
626
627