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/MainWindow.cpp
Views: 1401
1
// Copyright (c) 2012- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
// TODO: Get rid of the internal window.
19
// Tried before but Intel drivers screw up when minimizing, or something ?
20
21
#include "stdafx.h"
22
23
#include "ppsspp_config.h"
24
25
#include "Common/CommonWindows.h"
26
#include "Common/OSVersion.h"
27
28
#include <Windowsx.h>
29
#include <shellapi.h>
30
#include <commctrl.h>
31
#include <string>
32
#include <dwmapi.h>
33
34
#include "Common/System/Display.h"
35
#include "Common/System/NativeApp.h"
36
#include "Common/System/System.h"
37
#include "Common/TimeUtil.h"
38
#include "Common/StringUtils.h"
39
#include "Common/Data/Text/I18n.h"
40
#include "Common/Input/InputState.h"
41
#include "Common/Input/KeyCodes.h"
42
#include "Common/Thread/ThreadUtil.h"
43
#include "Common/Data/Encoding/Utf8.h"
44
45
#include "Core/Core.h"
46
#include "Core/Config.h"
47
#include "Core/ConfigValues.h"
48
#include "Core/Debugger/SymbolMap.h"
49
#include "Core/Instance.h"
50
#include "Core/KeyMap.h"
51
#include "Core/MIPS/JitCommon/JitCommon.h"
52
#include "Core/MIPS/JitCommon/JitBlockCache.h"
53
#include "Core/Reporting.h"
54
#include "Windows/InputBox.h"
55
#include "Windows/InputDevice.h"
56
#if PPSSPP_API(ANY_GL)
57
#include "Windows/GPU/WindowsGLContext.h"
58
#include "Windows/GEDebugger/GEDebugger.h"
59
#endif
60
#include "Windows/W32Util/DarkMode.h"
61
#include "Windows/W32Util/UAHMenuBar.h"
62
#include "Windows/Debugger/Debugger_Disasm.h"
63
#include "Windows/Debugger/Debugger_MemoryDlg.h"
64
65
#include "Common/GraphicsContext.h"
66
67
#include "Windows/main.h"
68
#ifndef _M_ARM
69
#include "Windows/DinputDevice.h"
70
#endif
71
#include "Windows/EmuThread.h"
72
#include "Windows/resource.h"
73
74
#include "Windows/MainWindow.h"
75
#include "Common/Log/LogManager.h"
76
#include "Common/Log/ConsoleListener.h"
77
#include "Windows/W32Util/DialogManager.h"
78
#include "Windows/W32Util/ShellUtil.h"
79
#include "Windows/W32Util/Misc.h"
80
#include "Windows/RawInput.h"
81
#include "Windows/CaptureDevice.h"
82
#include "Windows/TouchInputHandler.h"
83
#include "Windows/MainWindowMenu.h"
84
#include "GPU/GPUInterface.h"
85
#include "UI/OnScreenDisplay.h"
86
#include "UI/GameSettingsScreen.h"
87
88
#define MOUSEEVENTF_FROMTOUCH_NOPEN 0xFF515780 //http://msdn.microsoft.com/en-us/library/windows/desktop/ms703320(v=vs.85).aspx
89
#define MOUSEEVENTF_MASK_PLUS_PENTOUCH 0xFFFFFF80
90
91
// See https://github.com/unknownbrackets/verysleepy/commit/fc1b1b3bd6081fae3566cdb542d896e413238b71
92
int verysleepy__useSendMessage = 1;
93
94
const UINT WM_VERYSLEEPY_MSG = WM_APP + 0x3117;
95
const UINT WM_USER_GET_BASE_POINTER = WM_APP + 0x3118; // 0xB118
96
const UINT WM_USER_GET_EMULATION_STATE = WM_APP + 0x3119; // 0xB119
97
98
// Respond TRUE to a message with this param value to indicate support.
99
const WPARAM VERYSLEEPY_WPARAM_SUPPORTED = 0;
100
// Respond TRUE to a message wit this param value after filling in the addr name.
101
const WPARAM VERYSLEEPY_WPARAM_GETADDRINFO = 1;
102
103
struct VerySleepy_AddrInfo {
104
// Always zero for now.
105
int flags;
106
// This is the pointer (always passed as 64 bits.)
107
unsigned long long addr;
108
// Write the name here.
109
wchar_t name[256];
110
};
111
112
static std::wstring windowTitle;
113
114
#define TIMER_CURSORUPDATE 1
115
#define TIMER_CURSORMOVEUPDATE 2
116
#define CURSORUPDATE_INTERVAL_MS 1000
117
#define CURSORUPDATE_MOVE_TIMESPAN_MS 500
118
119
namespace MainWindow
120
{
121
HWND hwndMain;
122
HWND hwndDisplay;
123
HWND hwndGameList;
124
TouchInputHandler touchHandler;
125
static HMENU menu;
126
127
HINSTANCE hInst;
128
static int cursorCounter = 0;
129
static int prevCursorX = -1;
130
static int prevCursorY = -1;
131
132
static bool mouseButtonDown = false;
133
static bool hideCursor = false;
134
static int g_WindowState;
135
static bool g_IgnoreWM_SIZE = false;
136
static bool inFullscreenResize = false;
137
static bool inResizeMove = false;
138
static bool hasFocus = true;
139
static bool g_isFullscreen = false;
140
static bool g_keepScreenBright = false;
141
142
static bool disasmMapLoadPending = false;
143
static bool memoryMapLoadPending = false;
144
145
// gross hack
146
bool noFocusPause = false; // TOGGLE_PAUSE state to override pause on lost focus
147
bool trapMouse = true; // Handles some special cases(alt+tab, win menu) when game is running and mouse is confined
148
149
#define MAX_LOADSTRING 100
150
const TCHAR *szWindowClass = TEXT("PPSSPPWnd");
151
const TCHAR *szDisplayClass = TEXT("PPSSPPDisplay");
152
153
// Forward declarations of functions included in this code module:
154
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
155
LRESULT CALLBACK DisplayProc(HWND, UINT, WPARAM, LPARAM);
156
157
HWND GetHWND() {
158
return hwndMain;
159
}
160
161
HWND GetDisplayHWND() {
162
return hwndDisplay;
163
}
164
165
void SetKeepScreenBright(bool keepBright) {
166
g_keepScreenBright = keepBright;
167
}
168
169
void Init(HINSTANCE hInstance) {
170
// Register classes - Main Window
171
WNDCLASSEX wcex;
172
memset(&wcex, 0, sizeof(wcex));
173
wcex.cbSize = sizeof(WNDCLASSEX);
174
wcex.style = 0; // Show in taskbar
175
wcex.lpfnWndProc = (WNDPROC)WndProc;
176
wcex.hInstance = hInstance;
177
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
178
wcex.hbrBackground = NULL; // Always covered by display window
179
wcex.lpszMenuName = (LPCWSTR)IDR_MENU1;
180
wcex.lpszClassName = szWindowClass;
181
wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_PPSSPP);
182
wcex.hIconSm = (HICON)LoadImage(hInstance, (LPCTSTR)IDI_PPSSPP, IMAGE_ICON, 16, 16, LR_SHARED);
183
RegisterClassEx(&wcex);
184
185
WNDCLASSEX wcdisp;
186
memset(&wcdisp, 0, sizeof(wcdisp));
187
// Display Window (contained in main window)
188
wcdisp.cbSize = sizeof(WNDCLASSEX);
189
wcdisp.style = CS_HREDRAW | CS_VREDRAW;
190
wcdisp.lpfnWndProc = (WNDPROC)DisplayProc;
191
wcdisp.hInstance = hInstance;
192
wcdisp.hCursor = LoadCursor(NULL, IDC_ARROW);
193
wcdisp.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
194
wcdisp.lpszMenuName = 0;
195
wcdisp.lpszClassName = szDisplayClass;
196
wcdisp.hIcon = 0;
197
wcdisp.hIconSm = 0;
198
RegisterClassEx(&wcdisp);
199
}
200
201
void SavePosition() {
202
if (g_Config.UseFullScreen() || inFullscreenResize)
203
return;
204
205
WINDOWPLACEMENT placement{};
206
GetWindowPlacement(hwndMain, &placement);
207
if (placement.showCmd == SW_SHOWNORMAL) {
208
RECT rc;
209
GetWindowRect(hwndMain, &rc);
210
g_Config.iWindowX = rc.left;
211
g_Config.iWindowY = rc.top;
212
g_Config.iWindowWidth = rc.right - rc.left;
213
g_Config.iWindowHeight = rc.bottom - rc.top;
214
}
215
}
216
217
static void GetWindowSizeAtResolution(int xres, int yres, int *windowWidth, int *windowHeight) {
218
RECT rc{};
219
rc.right = xres;
220
rc.bottom = yres;
221
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, TRUE);
222
*windowWidth = rc.right - rc.left;
223
*windowHeight = rc.bottom - rc.top;
224
}
225
226
void SetWindowSize(int zoom) {
227
AssertCurrentThreadName("Main");
228
// Actually, auto mode should be more granular...
229
int width, height;
230
if (g_Config.IsPortrait()) {
231
GetWindowSizeAtResolution(272 * (int)zoom, 480 * (int)zoom, &width, &height);
232
} else {
233
GetWindowSizeAtResolution(480 * (int)zoom, 272 * (int)zoom, &width, &height);
234
}
235
g_Config.iWindowWidth = width;
236
g_Config.iWindowHeight = height;
237
MoveWindow(hwndMain, g_Config.iWindowX, g_Config.iWindowY, width, height, TRUE);
238
}
239
240
void SetInternalResolution(int res) {
241
if (res >= 0 && res <= RESOLUTION_MAX)
242
g_Config.iInternalResolution = res;
243
else {
244
if (++g_Config.iInternalResolution > RESOLUTION_MAX)
245
g_Config.iInternalResolution = 0;
246
}
247
248
System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED);
249
}
250
251
void CorrectCursor() {
252
bool autoHide = ((g_Config.UseFullScreen() && !mouseButtonDown) || (g_Config.bMouseControl && trapMouse)) && GetUIState() == UISTATE_INGAME;
253
if (autoHide && (hideCursor || g_Config.bMouseControl)) {
254
while (cursorCounter >= 0) {
255
cursorCounter = ShowCursor(FALSE);
256
}
257
if (g_Config.bMouseConfine) {
258
RECT rc;
259
GetClientRect(hwndDisplay, &rc);
260
ClientToScreen(hwndDisplay, reinterpret_cast<POINT*>(&rc.left));
261
ClientToScreen(hwndDisplay, reinterpret_cast<POINT*>(&rc.right));
262
ClipCursor(&rc);
263
}
264
} else {
265
hideCursor = !autoHide;
266
if (cursorCounter < 0) {
267
cursorCounter = ShowCursor(TRUE);
268
SetCursor(LoadCursor(NULL, IDC_ARROW));
269
ClipCursor(NULL);
270
}
271
}
272
}
273
274
static void HandleSizeChange(int newSizingType) {
275
SavePosition();
276
Core_NotifyWindowHidden(false);
277
if (!g_Config.bPauseWhenMinimized) {
278
System_PostUIMessage(UIMessage::WINDOW_MINIMIZED, "false");
279
}
280
281
int width, height;
282
W32Util::GetWindowRes(hwndMain, &width, &height);
283
284
// Moves the internal display window to match the inner size of the main window.
285
MoveWindow(hwndDisplay, 0, 0, width, height, TRUE);
286
287
// Setting pixelWidth to be too small could have odd consequences.
288
if (width >= 4 && height >= 4) {
289
// The framebuffer manager reads these once per frame, hopefully safe enough.. should really use a mutex or some
290
// much better mechanism.
291
PSP_CoreParameter().pixelWidth = width;
292
PSP_CoreParameter().pixelHeight = height;
293
}
294
295
DEBUG_LOG(Log::System, "Pixel width/height: %dx%d", PSP_CoreParameter().pixelWidth, PSP_CoreParameter().pixelHeight);
296
297
if (UpdateScreenScale(width, height)) {
298
System_PostUIMessage(UIMessage::GPU_DISPLAY_RESIZED);
299
System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED);
300
}
301
302
// Don't save the window state if fullscreen.
303
if (!g_Config.UseFullScreen()) {
304
g_WindowState = newSizingType;
305
}
306
}
307
308
void ToggleFullscreen(HWND hWnd, bool goingFullscreen) {
309
GraphicsContext *graphicsContext = PSP_CoreParameter().graphicsContext;
310
// Make sure no rendering is happening during the switch.
311
if (graphicsContext) {
312
graphicsContext->Pause();
313
}
314
315
WINDOWPLACEMENT placement = { sizeof(WINDOWPLACEMENT) };
316
GetWindowPlacement(hwndMain, &placement);
317
318
int oldWindowState = g_WindowState;
319
inFullscreenResize = true;
320
g_IgnoreWM_SIZE = true;
321
322
DWORD dwStyle;
323
324
if (!goingFullscreen) {
325
dwStyle = ::GetWindowLong(hWnd, GWL_STYLE);
326
327
// Remove popup
328
dwStyle &= ~WS_POPUP;
329
// Re-add caption and border styles.
330
dwStyle |= WS_OVERLAPPEDWINDOW;
331
} else {
332
// If the window was maximized before going fullscreen, make sure to restore first
333
// in order not to have the taskbar show up on top of PPSSPP.
334
if (oldWindowState == SIZE_MAXIMIZED || placement.showCmd == SW_SHOWMAXIMIZED) {
335
ShowWindow(hwndMain, SW_RESTORE);
336
}
337
338
// Remove caption and border styles.
339
dwStyle = ::GetWindowLong(hWnd, GWL_STYLE);
340
dwStyle &= ~WS_OVERLAPPEDWINDOW;
341
// Add Popup
342
dwStyle |= WS_POPUP;
343
}
344
345
::SetWindowLong(hWnd, GWL_STYLE, dwStyle);
346
347
// Remove the menu bar. This can trigger WM_SIZE because the contents change size.
348
::SetMenu(hWnd, goingFullscreen || !g_Config.bShowMenuBar ? NULL : menu);
349
350
if (g_Config.UseFullScreen() != goingFullscreen) {
351
g_Config.bFullScreen = goingFullscreen;
352
g_Config.iForceFullScreen = -1;
353
}
354
g_isFullscreen = goingFullscreen;
355
356
g_IgnoreWM_SIZE = false;
357
358
// Resize to the appropriate view.
359
// If we're returning to window mode, re-apply the appropriate size setting.
360
if (goingFullscreen) {
361
if (g_Config.bFullScreenMulti) {
362
// Maximize isn't enough to display on all monitors.
363
// Remember that negative coordinates may be valid.
364
int totalX = GetSystemMetrics(SM_XVIRTUALSCREEN);
365
int totalY = GetSystemMetrics(SM_YVIRTUALSCREEN);
366
int totalWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
367
int totalHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
368
MoveWindow(hwndMain, totalX, totalY, totalWidth, totalHeight, TRUE);
369
HandleSizeChange(oldWindowState);
370
ShowWindow(hwndMain, SW_SHOW);
371
} else {
372
ShowWindow(hwndMain, SW_MAXIMIZE);
373
}
374
} else {
375
ShowWindow(hwndMain, oldWindowState == SIZE_MAXIMIZED ? SW_MAXIMIZE : SW_RESTORE);
376
if (g_Config.bFullScreenMulti && oldWindowState != SIZE_MAXIMIZED) {
377
// Return the screen to where it was.
378
MoveWindow(hwndMain, g_Config.iWindowX, g_Config.iWindowY, g_Config.iWindowWidth, g_Config.iWindowHeight, TRUE);
379
}
380
if (oldWindowState == SIZE_MAXIMIZED) {
381
// WM_SIZE wasn't sent, since the size didn't change (it was full screen before and after.)
382
HandleSizeChange(oldWindowState);
383
}
384
}
385
386
inFullscreenResize = false;
387
CorrectCursor();
388
389
ShowOwnedPopups(hwndMain, goingFullscreen ? FALSE : TRUE);
390
W32Util::MakeTopMost(hwndMain, g_Config.bTopMost);
391
392
WindowsRawInput::NotifyMenu();
393
394
if (graphicsContext) {
395
graphicsContext->Resume();
396
}
397
}
398
399
void Minimize() {
400
ShowWindow(hwndMain, SW_MINIMIZE);
401
InputDevice::LoseFocus();
402
}
403
404
RECT DetermineWindowRectangle() {
405
const int virtualScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
406
const int virtualScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
407
const int virtualScreenX = GetSystemMetrics(SM_XVIRTUALSCREEN);
408
const int virtualScreenY = GetSystemMetrics(SM_YVIRTUALSCREEN);
409
const int currentScreenWidth = GetSystemMetrics(SM_CXSCREEN);
410
const int currentScreenHeight = GetSystemMetrics(SM_CYSCREEN);
411
412
bool resetPositionX = true;
413
bool resetPositionY = true;
414
415
if (g_Config.iWindowWidth > 0 && g_Config.iWindowHeight > 0 && !g_Config.UseFullScreen()) {
416
bool visibleHorizontally = ((g_Config.iWindowX + g_Config.iWindowWidth) >= virtualScreenX) &&
417
((g_Config.iWindowX + g_Config.iWindowWidth) < (virtualScreenWidth + g_Config.iWindowWidth));
418
419
bool visibleVertically = ((g_Config.iWindowY + g_Config.iWindowHeight) >= virtualScreenY) &&
420
((g_Config.iWindowY + g_Config.iWindowHeight) < (virtualScreenHeight + g_Config.iWindowHeight));
421
422
if (visibleHorizontally)
423
resetPositionX = false;
424
if (visibleVertically)
425
resetPositionY = false;
426
}
427
428
// Try to workaround #9563.
429
if (!resetPositionY && g_Config.iWindowY < 0) {
430
g_Config.iWindowY = 0;
431
}
432
433
int windowWidth = g_Config.iWindowWidth;
434
int windowHeight = g_Config.iWindowHeight;
435
436
// First, get the w/h right.
437
if (windowWidth <= 0 || windowHeight <= 0) {
438
bool portrait = g_Config.IsPortrait();
439
440
// We want to adjust for DPI but still get an integer pixel scaling ratio.
441
double dpi_scale = 96.0 / System_GetPropertyFloat(SYSPROP_DISPLAY_DPI);
442
int scale = (int)ceil(2.0 / dpi_scale);
443
444
GetWindowSizeAtResolution(scale * (portrait ? 272 : 480), scale * (portrait ? 480 : 272), &windowWidth, &windowHeight);
445
}
446
447
// Then center if necessary. One dimension at a time.
448
// Max is to make sure that if we end up making the window bigger than the screen (which is not ideal), the top left
449
// corner, and thus the menu etc, will be visible. Also potential workaround for #9563.
450
int x = g_Config.iWindowX;
451
int y = g_Config.iWindowY;
452
if (resetPositionX) {
453
x = std::max(0, (currentScreenWidth - windowWidth) / 2);
454
}
455
if (resetPositionY) {
456
y = std::max(0, (currentScreenHeight - windowHeight) / 2);
457
}
458
459
RECT rc;
460
rc.left = x;
461
rc.right = rc.left + windowWidth;
462
rc.top = y;
463
rc.bottom = rc.top + windowHeight;
464
return rc;
465
}
466
467
void UpdateWindowTitle() {
468
std::wstring title = windowTitle;
469
if (PPSSPP_ID >= 1 && GetInstancePeerCount() > 1) {
470
title.append(ConvertUTF8ToWString(StringFromFormat(" (instance: %d)", (int)PPSSPP_ID)));
471
}
472
SetWindowText(hwndMain, title.c_str());
473
}
474
475
void SetWindowTitle(const wchar_t *title) {
476
windowTitle = title;
477
}
478
479
BOOL Show(HINSTANCE hInstance) {
480
hInst = hInstance; // Store instance handle in our global variable.
481
RECT rc = DetermineWindowRectangle();
482
483
u32 style = WS_OVERLAPPEDWINDOW;
484
485
hwndMain = CreateWindowEx(0,szWindowClass, L"", style,
486
rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInstance, NULL);
487
if (!hwndMain)
488
return FALSE;
489
490
SetWindowLong(hwndMain, GWL_EXSTYLE, WS_EX_APPWINDOW);
491
492
493
const DWM_WINDOW_CORNER_PREFERENCE pref = DWMWCP_DONOTROUND;
494
DwmSetWindowAttribute(hwndMain, DWMWA_WINDOW_CORNER_PREFERENCE, &pref, sizeof(pref));
495
496
RECT rcClient;
497
GetClientRect(hwndMain, &rcClient);
498
499
hwndDisplay = CreateWindowEx(0, szDisplayClass, L"", WS_CHILD | WS_VISIBLE,
500
0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, hwndMain, 0, hInstance, 0);
501
if (!hwndDisplay)
502
return FALSE;
503
504
menu = GetMenu(hwndMain);
505
506
MENUINFO info;
507
ZeroMemory(&info,sizeof(MENUINFO));
508
info.cbSize = sizeof(MENUINFO);
509
info.cyMax = 0;
510
info.dwStyle = MNS_CHECKORBMP;
511
info.fMask = MIM_STYLE;
512
for (int i = 0; i < GetMenuItemCount(menu); i++) {
513
SetMenuInfo(GetSubMenu(menu,i), &info);
514
}
515
516
// Always translate first: translating resets the menu.
517
TranslateMenus(hwndMain, menu);
518
UpdateMenus();
519
520
// Accept dragged files.
521
DragAcceptFiles(hwndMain, TRUE);
522
523
hideCursor = true;
524
SetTimer(hwndMain, TIMER_CURSORUPDATE, CURSORUPDATE_INTERVAL_MS, 0);
525
526
ToggleFullscreen(hwndMain, g_Config.UseFullScreen());
527
528
W32Util::MakeTopMost(hwndMain, g_Config.bTopMost);
529
530
touchHandler.registerTouchWindow(hwndDisplay);
531
532
WindowsRawInput::Init();
533
534
SetFocus(hwndMain);
535
536
return TRUE;
537
}
538
539
void CreateDisasmWindow() {
540
if (!disasmWindow) {
541
disasmWindow = new CDisasm(MainWindow::GetHInstance(), MainWindow::GetHWND(), currentDebugMIPS);
542
DialogManager::AddDlg(disasmWindow);
543
}
544
if (disasmMapLoadPending)
545
disasmWindow->NotifyMapLoaded();
546
disasmMapLoadPending = false;
547
}
548
549
void CreateGeDebuggerWindow() {
550
#if PPSSPP_API(ANY_GL)
551
if (!geDebuggerWindow) {
552
geDebuggerWindow = new CGEDebugger(MainWindow::GetHInstance(), MainWindow::GetHWND());
553
DialogManager::AddDlg(geDebuggerWindow);
554
}
555
#endif
556
}
557
558
void CreateMemoryWindow() {
559
if (!memoryWindow) {
560
memoryWindow = new CMemoryDlg(MainWindow::GetHInstance(), MainWindow::GetHWND(), currentDebugMIPS);
561
DialogManager::AddDlg(memoryWindow);
562
}
563
if (memoryMapLoadPending)
564
memoryWindow->NotifyMapLoaded();
565
memoryMapLoadPending = false;
566
}
567
568
void CreateVFPUWindow() {
569
if (!vfpudlg) {
570
vfpudlg = new CVFPUDlg(MainWindow::GetHInstance(), MainWindow::GetHWND(), currentDebugMIPS);
571
DialogManager::AddDlg(vfpudlg);
572
}
573
}
574
575
void NotifyDebuggerMapLoaded() {
576
disasmMapLoadPending = disasmWindow == nullptr;
577
memoryMapLoadPending = memoryWindow == nullptr;
578
if (!disasmMapLoadPending)
579
disasmWindow->NotifyMapLoaded();
580
if (!memoryMapLoadPending)
581
memoryWindow->NotifyMapLoaded();
582
}
583
584
void DestroyDebugWindows() {
585
DialogManager::RemoveDlg(disasmWindow);
586
delete disasmWindow;
587
disasmWindow = nullptr;
588
589
#if PPSSPP_API(ANY_GL)
590
DialogManager::RemoveDlg(geDebuggerWindow);
591
delete geDebuggerWindow;
592
geDebuggerWindow = nullptr;
593
#endif
594
595
DialogManager::RemoveDlg(memoryWindow);
596
delete memoryWindow;
597
memoryWindow = nullptr;
598
599
DialogManager::RemoveDlg(vfpudlg);
600
delete vfpudlg;
601
vfpudlg = nullptr;
602
}
603
604
LRESULT CALLBACK DisplayProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
605
static bool firstErase = true;
606
607
switch (message) {
608
case WM_SIZE:
609
break;
610
611
case WM_SETFOCUS:
612
break;
613
614
case WM_ERASEBKGND:
615
if (firstErase) {
616
firstErase = false;
617
// Paint black on first erase while OpenGL stuff is loading
618
return DefWindowProc(hWnd, message, wParam, lParam);
619
}
620
// Then never erase, let the OpenGL drawing take care of everything.
621
return 1;
622
623
// Mouse input. We send asynchronous touch events for minimal latency.
624
case WM_LBUTTONDOWN:
625
if (!touchHandler.hasTouch() ||
626
(GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN)
627
{
628
// Hack: Take the opportunity to show the cursor.
629
mouseButtonDown = true;
630
631
float x = GET_X_LPARAM(lParam) * g_display.dpi_scale_x;
632
float y = GET_Y_LPARAM(lParam) * g_display.dpi_scale_y;
633
WindowsRawInput::SetMousePos(x, y);
634
635
TouchInput touch;
636
touch.id = 0;
637
touch.flags = TOUCH_DOWN;
638
touch.x = x;
639
touch.y = y;
640
NativeTouch(touch);
641
SetCapture(hWnd);
642
643
// Simulate doubleclick, doesn't work with RawInput enabled
644
static double lastMouseDown;
645
static float lastMouseDownX = -1.0f;
646
static float lastMouseDownY = -1.0f;
647
double now = time_now_d();
648
if ((now - lastMouseDown) < 0.001 * GetDoubleClickTime()) {
649
float dx = lastMouseDownX - x;
650
float dy = lastMouseDownY - y;
651
float distSq = dx * dx + dy * dy;
652
if (distSq < 3.0f*3.0f && !g_Config.bShowTouchControls && !g_Config.bMouseControl && GetUIState() == UISTATE_INGAME && g_Config.bFullscreenOnDoubleclick) {
653
SendToggleFullscreen(!g_Config.UseFullScreen());
654
}
655
lastMouseDown = 0.0;
656
} else {
657
lastMouseDown = now;
658
}
659
lastMouseDownX = x;
660
lastMouseDownY = y;
661
}
662
break;
663
664
case WM_MOUSEMOVE:
665
if (!touchHandler.hasTouch() ||
666
(GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN)
667
{
668
// Hack: Take the opportunity to show the cursor.
669
mouseButtonDown = (wParam & MK_LBUTTON) != 0;
670
int cursorX = GET_X_LPARAM(lParam);
671
int cursorY = GET_Y_LPARAM(lParam);
672
if (abs(cursorX - prevCursorX) > 1 || abs(cursorY - prevCursorY) > 1) {
673
hideCursor = false;
674
SetTimer(hwndMain, TIMER_CURSORMOVEUPDATE, CURSORUPDATE_MOVE_TIMESPAN_MS, 0);
675
}
676
prevCursorX = cursorX;
677
prevCursorY = cursorY;
678
679
float x = (float)cursorX * g_display.dpi_scale_x;
680
float y = (float)cursorY * g_display.dpi_scale_y;
681
WindowsRawInput::SetMousePos(x, y);
682
683
if (wParam & MK_LBUTTON) {
684
TouchInput touch;
685
touch.id = 0;
686
touch.flags = TOUCH_MOVE;
687
touch.x = x;
688
touch.y = y;
689
NativeTouch(touch);
690
}
691
}
692
break;
693
694
case WM_LBUTTONUP:
695
if (!touchHandler.hasTouch() ||
696
(GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN)
697
{
698
// Hack: Take the opportunity to hide the cursor.
699
mouseButtonDown = false;
700
701
float x = (float)GET_X_LPARAM(lParam) * g_display.dpi_scale_x;
702
float y = (float)GET_Y_LPARAM(lParam) * g_display.dpi_scale_y;
703
WindowsRawInput::SetMousePos(x, y);
704
705
TouchInput touch;
706
touch.id = 0;
707
touch.flags = TOUCH_UP;
708
touch.x = x;
709
touch.y = y;
710
NativeTouch(touch);
711
ReleaseCapture();
712
}
713
break;
714
715
case WM_TOUCH:
716
touchHandler.handleTouchEvent(hWnd, message, wParam, lParam);
717
return 0;
718
719
default:
720
return DefWindowProc(hWnd, message, wParam, lParam);
721
}
722
return 0;
723
}
724
725
RECT MapRectFromClientToWndCoords(HWND hwnd, const RECT & r)
726
{
727
RECT wnd_coords = r;
728
729
// map to screen
730
MapWindowPoints(hwnd, NULL, reinterpret_cast<POINT *>(&wnd_coords), 2);
731
732
RECT scr_coords;
733
GetWindowRect(hwnd, &scr_coords);
734
735
// map to window coords by substracting the window coord origin in
736
// screen coords.
737
OffsetRect(&wnd_coords, -scr_coords.left, -scr_coords.top);
738
739
return wnd_coords;
740
}
741
742
RECT GetNonclientMenuBorderRect(HWND hwnd)
743
{
744
RECT r;
745
GetClientRect(hwnd, &r);
746
r = MapRectFromClientToWndCoords(hwnd, r);
747
int y = r.top - 1;
748
return {
749
r.left,
750
y,
751
r.right,
752
y + 1
753
};
754
}
755
756
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
757
LRESULT darkResult = 0;
758
if (UAHDarkModeWndProc(hWnd, message, wParam, lParam, &darkResult)) {
759
return darkResult;
760
}
761
762
switch (message) {
763
case WM_CREATE:
764
if (!IsVistaOrHigher()) {
765
// Remove the D3D11 choice on versions below XP
766
RemoveMenu(GetMenu(hWnd), ID_OPTIONS_DIRECT3D11, MF_BYCOMMAND);
767
}
768
if (g_darkModeSupported) {
769
SendMessageW(hWnd, WM_THEMECHANGED, 0, 0);
770
}
771
break;
772
773
case WM_USER_RUN_CALLBACK:
774
{
775
auto callback = reinterpret_cast<void (*)(void *window, void *userdata)>(wParam);
776
void *userdata = reinterpret_cast<void *>(lParam);
777
callback(hWnd, userdata);
778
break;
779
}
780
case WM_USER_GET_BASE_POINTER:
781
Reporting::NotifyDebugger();
782
switch (lParam) {
783
case 0: return (u32)(u64)Memory::base;
784
case 1: return (u32)((u64)Memory::base >> 32);
785
case 2: return (u32)(u64)(&Memory::base);
786
case 3: return (u32)((u64)(&Memory::base) >> 32);
787
default:
788
return 0;
789
}
790
break;
791
792
case WM_USER_GET_EMULATION_STATE:
793
return (u32)(Core_IsActive() && GetUIState() == UISTATE_INGAME);
794
795
// Hack to kill the white line underneath the menubar.
796
// From https://stackoverflow.com/questions/57177310/how-to-paint-over-white-line-between-menu-bar-and-client-area-of-window
797
case WM_NCPAINT:
798
case WM_NCACTIVATE:
799
{
800
if (!IsDarkModeEnabled() || IsIconic(hWnd)) {
801
return DefWindowProc(hWnd, message, wParam, lParam);
802
}
803
804
auto result = DefWindowProc(hWnd, message, wParam, lParam);
805
// Paint over the line with pure black. Could also try to figure out the dark theme color.
806
HDC hdc = GetWindowDC(hWnd);
807
RECT r = GetNonclientMenuBorderRect(hWnd);
808
HBRUSH red = CreateSolidBrush(RGB(0, 0, 0));
809
FillRect(hdc, &r, red);
810
DeleteObject(red);
811
ReleaseDC(hWnd, hdc);
812
return result;
813
}
814
815
case WM_GETMINMAXINFO:
816
{
817
MINMAXINFO *minmax = reinterpret_cast<MINMAXINFO *>(lParam);
818
RECT rc = { 0 };
819
bool portrait = g_Config.IsPortrait();
820
rc.right = portrait ? 272 : 480;
821
rc.bottom = portrait ? 480 : 272;
822
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, TRUE);
823
minmax->ptMinTrackSize.x = rc.right - rc.left;
824
minmax->ptMinTrackSize.y = rc.bottom - rc.top;
825
}
826
return 0;
827
828
case WM_ACTIVATE:
829
{
830
UpdateWindowTitle();
831
bool pause = true;
832
if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) {
833
WindowsRawInput::GainFocus();
834
if (!IsIconic(GetHWND())) {
835
InputDevice::GainFocus();
836
}
837
g_activeWindow = WINDOW_MAINWINDOW;
838
pause = false;
839
} else {
840
g_activeWindow = WINDOW_OTHER;
841
}
842
if (!noFocusPause && g_Config.bPauseOnLostFocus && GetUIState() == UISTATE_INGAME) {
843
if (pause != Core_IsStepping()) {
844
if (disasmWindow)
845
SendMessage(disasmWindow->GetDlgHandle(), WM_COMMAND, IDC_STOPGO, 0);
846
else
847
Core_EnableStepping(pause, "ui.lost_focus", 0);
848
}
849
}
850
851
if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) {
852
System_PostUIMessage(UIMessage::GOT_FOCUS);
853
hasFocus = true;
854
trapMouse = true;
855
}
856
if (wParam == WA_INACTIVE) {
857
System_PostUIMessage(UIMessage::LOST_FOCUS);
858
WindowsRawInput::LoseFocus();
859
InputDevice::LoseFocus();
860
hasFocus = false;
861
trapMouse = false;
862
}
863
}
864
break;
865
866
case WM_SETFOCUS:
867
UpdateWindowTitle();
868
break;
869
870
case WM_ERASEBKGND:
871
// This window is always covered by DisplayWindow. No reason to erase.
872
return 0;
873
874
case WM_MOVE:
875
SavePosition();
876
break;
877
878
case WM_ENTERSIZEMOVE:
879
inResizeMove = true;
880
break;
881
882
case WM_EXITSIZEMOVE:
883
inResizeMove = false;
884
HandleSizeChange(SIZE_RESTORED);
885
break;
886
887
case WM_SIZE:
888
switch (wParam) {
889
case SIZE_RESTORED:
890
case SIZE_MAXIMIZED:
891
if (g_IgnoreWM_SIZE) {
892
return DefWindowProc(hWnd, message, wParam, lParam);
893
} else if (!inResizeMove) {
894
HandleSizeChange(wParam);
895
}
896
if (hasFocus) {
897
InputDevice::GainFocus();
898
}
899
break;
900
901
case SIZE_MINIMIZED:
902
Core_NotifyWindowHidden(true);
903
if (!g_Config.bPauseWhenMinimized) {
904
System_PostUIMessage(UIMessage::WINDOW_MINIMIZED, "true");
905
}
906
InputDevice::LoseFocus();
907
break;
908
default:
909
break;
910
}
911
break;
912
913
// Wheel events have to stay in WndProc for compatibility with older Windows(7). See #12156
914
case WM_MOUSEWHEEL:
915
{
916
int wheelDelta = (short)(wParam >> 16);
917
KeyInput key;
918
key.deviceId = DEVICE_ID_MOUSE;
919
920
if (wheelDelta < 0) {
921
key.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN;
922
wheelDelta = -wheelDelta;
923
} else {
924
key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP;
925
}
926
// There's no release event, but we simulate it in NativeKey/NativeFrame.
927
key.flags = KEY_DOWN | KEY_HASWHEELDELTA | (wheelDelta << 16);
928
NativeKey(key);
929
}
930
break;
931
932
case WM_TIMER:
933
// Hack: Take the opportunity to also show/hide the mouse cursor in fullscreen mode.
934
switch (wParam) {
935
case TIMER_CURSORUPDATE:
936
CorrectCursor();
937
return 0;
938
939
case TIMER_CURSORMOVEUPDATE:
940
hideCursor = true;
941
KillTimer(hWnd, TIMER_CURSORMOVEUPDATE);
942
return 0;
943
}
944
break;
945
946
case WM_COMMAND:
947
{
948
if (!MainThread_Ready())
949
return DefWindowProc(hWnd, message, wParam, lParam);
950
951
MainWindowMenu_Process(hWnd, wParam);
952
}
953
break;
954
955
case WM_USER_TOGGLE_FULLSCREEN:
956
ToggleFullscreen(hwndMain, wParam ? true : false);
957
break;
958
959
case WM_INPUT:
960
return WindowsRawInput::Process(hWnd, wParam, lParam);
961
962
// TODO: Could do something useful with WM_INPUT_DEVICE_CHANGE?
963
964
// Not sure why we are actually getting WM_CHAR even though we use RawInput, but alright..
965
case WM_CHAR:
966
return WindowsRawInput::ProcessChar(hWnd, wParam, lParam);
967
968
case WM_DEVICECHANGE:
969
#ifndef _M_ARM
970
DinputDevice::CheckDevices();
971
#endif
972
if (winCamera)
973
winCamera->CheckDevices();
974
if (winMic)
975
winMic->CheckDevices();
976
return DefWindowProc(hWnd, message, wParam, lParam);
977
978
case WM_VERYSLEEPY_MSG:
979
switch (wParam) {
980
case VERYSLEEPY_WPARAM_SUPPORTED:
981
return TRUE;
982
983
case VERYSLEEPY_WPARAM_GETADDRINFO:
984
{
985
VerySleepy_AddrInfo *info = (VerySleepy_AddrInfo *)lParam;
986
const u8 *ptr = (const u8 *)info->addr;
987
std::string name;
988
989
std::lock_guard<std::recursive_mutex> guard(MIPSComp::jitLock);
990
if (MIPSComp::jit && MIPSComp::jit->DescribeCodePtr(ptr, name)) {
991
swprintf_s(info->name, L"Jit::%S", name.c_str());
992
return TRUE;
993
}
994
if (gpu && gpu->DescribeCodePtr(ptr, name)) {
995
swprintf_s(info->name, L"GPU::%S", name.c_str());
996
return TRUE;
997
}
998
}
999
return FALSE;
1000
1001
default:
1002
return FALSE;
1003
}
1004
break;
1005
1006
case WM_DROPFILES:
1007
{
1008
if (!MainThread_Ready())
1009
return DefWindowProc(hWnd, message, wParam, lParam);
1010
1011
HDROP hdrop = (HDROP)wParam;
1012
int count = DragQueryFile(hdrop, 0xFFFFFFFF, 0, 0);
1013
if (count != 1) {
1014
// TODO: Translate? Or just not bother?
1015
MessageBox(hwndMain, L"You can only load one file at a time", L"Error", MB_ICONINFORMATION);
1016
} else {
1017
TCHAR filename[1024];
1018
if (DragQueryFile(hdrop, 0, filename, ARRAY_SIZE(filename)) != 0) {
1019
const std::string utf8_filename = ReplaceAll(ConvertWStringToUTF8(filename), "\\", "/");
1020
System_PostUIMessage(UIMessage::REQUEST_GAME_BOOT, utf8_filename);
1021
Core_EnableStepping(false);
1022
}
1023
}
1024
DragFinish(hdrop);
1025
}
1026
break;
1027
1028
case WM_CLOSE:
1029
InputDevice::StopPolling();
1030
MainThread_Stop();
1031
WindowsRawInput::Shutdown();
1032
return DefWindowProc(hWnd,message,wParam,lParam);
1033
1034
case WM_DESTROY:
1035
KillTimer(hWnd, TIMER_CURSORUPDATE);
1036
KillTimer(hWnd, TIMER_CURSORMOVEUPDATE);
1037
// Main window is gone, this tells the message loop to exit.
1038
PostQuitMessage(0);
1039
return 0;
1040
1041
case WM_USER + 1:
1042
NotifyDebuggerMapLoaded();
1043
if (disasmWindow)
1044
disasmWindow->UpdateDialog();
1045
break;
1046
1047
case WM_USER_SAVESTATE_FINISH:
1048
SetCursor(LoadCursor(0, IDC_ARROW));
1049
break;
1050
1051
case WM_USER_UPDATE_UI:
1052
TranslateMenus(hwndMain, menu);
1053
// Update checked status immediately for accelerators.
1054
UpdateMenus();
1055
break;
1056
1057
case WM_USER_WINDOW_TITLE_CHANGED:
1058
UpdateWindowTitle();
1059
break;
1060
1061
case WM_USER_RESTART_EMUTHREAD:
1062
NativeSetRestarting();
1063
InputDevice::StopPolling();
1064
MainThread_Stop();
1065
coreState = CORE_POWERUP;
1066
UpdateUIState(UISTATE_MENU);
1067
MainThread_Start(g_Config.iGPUBackend == (int)GPUBackend::OPENGL);
1068
InputDevice::BeginPolling();
1069
break;
1070
1071
case WM_USER_SWITCHUMD_UPDATED:
1072
UpdateSwitchUMD();
1073
break;
1074
1075
case WM_MENUSELECT:
1076
// Called when a menu is opened. Also when an item is selected, but meh.
1077
UpdateMenus(true);
1078
WindowsRawInput::NotifyMenu();
1079
trapMouse = false;
1080
break;
1081
1082
case WM_EXITMENULOOP:
1083
// Called when menu is closed.
1084
trapMouse = true;
1085
break;
1086
1087
// Turn off the screensaver if in-game.
1088
// Note that if there's a screensaver password, this simple method
1089
// doesn't work on Vista or higher.
1090
case WM_SYSCOMMAND:
1091
// Disable Alt key for menu if it's been mapped.
1092
if (wParam == SC_KEYMENU && (lParam >> 16) <= 0) {
1093
if (KeyMap::IsKeyMapped(DEVICE_ID_KEYBOARD, NKCODE_ALT_LEFT) || KeyMap::IsKeyMapped(DEVICE_ID_KEYBOARD, NKCODE_ALT_RIGHT)) {
1094
return 0;
1095
}
1096
}
1097
if (g_keepScreenBright) {
1098
switch (wParam) {
1099
case SC_SCREENSAVE:
1100
return 0;
1101
case SC_MONITORPOWER:
1102
if (lParam == 1 || lParam == 2) {
1103
return 0;
1104
} else {
1105
break;
1106
}
1107
default:
1108
// fall down to DefWindowProc
1109
break;
1110
}
1111
}
1112
return DefWindowProc(hWnd, message, wParam, lParam);
1113
case WM_SETTINGCHANGE:
1114
{
1115
if (g_darkModeSupported && IsColorSchemeChangeMessage(lParam))
1116
SendMessageW(hWnd, WM_THEMECHANGED, 0, 0);
1117
}
1118
return DefWindowProc(hWnd, message, wParam, lParam);
1119
1120
case WM_THEMECHANGED:
1121
{
1122
if (g_darkModeSupported)
1123
{
1124
_AllowDarkModeForWindow(hWnd, g_darkModeEnabled);
1125
RefreshTitleBarThemeColor(hWnd);
1126
}
1127
return DefWindowProc(hWnd, message, wParam, lParam);
1128
}
1129
1130
default:
1131
return DefWindowProc(hWnd, message, wParam, lParam);
1132
}
1133
return 0;
1134
}
1135
1136
void Redraw() {
1137
InvalidateRect(hwndDisplay,0,0);
1138
}
1139
1140
HINSTANCE GetHInstance() {
1141
return hInst;
1142
}
1143
1144
void ToggleDebugConsoleVisibility() {
1145
if (!g_Config.bEnableLogging) {
1146
LogManager::GetInstance()->GetConsoleListener()->Show(false);
1147
EnableMenuItem(menu, ID_DEBUG_LOG, MF_GRAYED);
1148
}
1149
else {
1150
LogManager::GetInstance()->GetConsoleListener()->Show(true);
1151
EnableMenuItem(menu, ID_DEBUG_LOG, MF_ENABLED);
1152
}
1153
}
1154
1155
void SendToggleFullscreen(bool fullscreen) {
1156
PostMessage(hwndMain, WM_USER_TOGGLE_FULLSCREEN, fullscreen, 0);
1157
}
1158
1159
bool IsFullscreen() {
1160
return g_isFullscreen;
1161
}
1162
1163
void RunCallbackInWndProc(void (*callback)(void *, void *), void *userdata) {
1164
PostMessage(hwndMain, WM_USER_RUN_CALLBACK, reinterpret_cast<WPARAM>(callback), reinterpret_cast<LPARAM>(userdata));
1165
}
1166
1167
} // namespace
1168
1169