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