Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Windows/MainWindow.cpp
5669 views
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> // For drag/drop functionality
30
#include <string>
31
#include <dwmapi.h>
32
33
#include "Common/System/Display.h"
34
#include "Common/System/NativeApp.h"
35
#include "Common/System/System.h"
36
#include "Common/TimeUtil.h"
37
#include "Common/StringUtils.h"
38
#include "Common/Data/Text/I18n.h"
39
#include "Common/Input/InputState.h"
40
#include "Common/Input/KeyCodes.h"
41
#include "Common/Thread/ThreadUtil.h"
42
#include "Common/Data/Encoding/Utf8.h"
43
#include "ext/imgui/imgui_impl_platform.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/Reporting.h"
53
#include "Windows/InputDevice.h"
54
#if PPSSPP_API(ANY_GL)
55
#include "Windows/GEDebugger/GEDebugger.h"
56
#endif
57
#include "Windows/W32Util/DarkMode.h"
58
#include "Windows/W32Util/UAHMenuBar.h"
59
#include "Windows/Debugger/Debugger_Disasm.h"
60
#include "Windows/Debugger/Debugger_MemoryDlg.h"
61
62
#include "Common/GraphicsContext.h"
63
64
#include "Windows/main.h"
65
#ifndef _M_ARM
66
#include "Windows/DinputDevice.h"
67
#endif
68
#include "Windows/EmuThread.h"
69
#include "Windows/resource.h"
70
71
#include "Windows/MainWindow.h"
72
#include "Common/Log/LogManager.h"
73
#include "Common/Log/ConsoleListener.h"
74
#include "Windows/W32Util/DialogManager.h"
75
#include "Windows/W32Util/Misc.h"
76
#include "Windows/RawInput.h"
77
#include "Windows/CaptureDevice.h"
78
#include "Windows/TouchInputHandler.h"
79
#include "Windows/MainWindowMenu.h"
80
#include "GPU/GPUCommon.h"
81
#include "UI/PauseScreen.h"
82
83
#define MOUSEEVENTF_FROMTOUCH_NOPEN 0xFF515780 //http://msdn.microsoft.com/en-us/library/windows/desktop/ms703320(v=vs.85).aspx
84
#define MOUSEEVENTF_MASK_PLUS_PENTOUCH 0xFFFFFF80
85
86
// See https://github.com/unknownbrackets/verysleepy/commit/fc1b1b3bd6081fae3566cdb542d896e413238b71
87
int verysleepy__useSendMessage = 1;
88
89
const UINT WM_VERYSLEEPY_MSG = WM_APP + 0x3117;
90
const UINT WM_USER_GET_BASE_POINTER = WM_APP + 0x3118; // 0xB118
91
const UINT WM_USER_GET_EMULATION_STATE = WM_APP + 0x3119; // 0xB119
92
const UINT WM_USER_GET_CURRENT_GAMEID = WM_APP + 0x311A; // 0xB11A
93
const UINT WM_USER_GET_MODULE_INFO = WM_APP + 0x311B; // 0xB11B
94
95
// Respond TRUE to a message with this param value to indicate support.
96
const WPARAM VERYSLEEPY_WPARAM_SUPPORTED = 0;
97
// Respond TRUE to a message wit this param value after filling in the addr name.
98
const WPARAM VERYSLEEPY_WPARAM_GETADDRINFO = 1;
99
100
struct VerySleepy_AddrInfo {
101
// Always zero for now.
102
int flags;
103
// This is the pointer (always passed as 64 bits.)
104
unsigned long long addr;
105
// Write the name here.
106
wchar_t name[256];
107
};
108
109
static std::mutex g_windowTitleLock;
110
static std::wstring g_windowTitle;
111
112
#define TIMER_CURSORUPDATE 1
113
#define TIMER_CURSORMOVEUPDATE 2
114
#define CURSORUPDATE_INTERVAL_MS 1000
115
#define CURSORUPDATE_MOVE_TIMESPAN_MS 500
116
117
inline WindowSizeState ShowCmdToWindowSizeState(const int showCmd) {
118
switch (showCmd) {
119
case SW_SHOWMAXIMIZED:
120
return WindowSizeState::Maximized;
121
case SW_SHOWMINIMIZED:
122
return WindowSizeState::Minimized;
123
case SW_SHOWNORMAL:
124
default:
125
return WindowSizeState::Normal;
126
}
127
}
128
129
inline int WindowSizeStateToShowCmd(const WindowSizeState windowSizeState) {
130
switch (windowSizeState) {
131
case WindowSizeState::Maximized:
132
return SW_SHOWMAXIMIZED;
133
case WindowSizeState::Minimized:
134
return SW_SHOWMINIMIZED;
135
default:
136
return SW_SHOWNORMAL;
137
}
138
}
139
140
static const char *WindowSizeStateToString(const WindowSizeState state) {
141
switch (state) {
142
case WindowSizeState::Normal:
143
return "Normal";
144
case WindowSizeState::Minimized:
145
return "Minimized";
146
case WindowSizeState::Maximized:
147
return "Maximized";
148
default:
149
return "Unknown";
150
}
151
}
152
153
namespace MainWindow {
154
static HWND hwndMain;
155
static TouchInputHandler touchHandler;
156
157
static HMENU g_hMenu;
158
159
HINSTANCE hInst;
160
static int cursorCounter = 0;
161
static int prevCursorX = -1;
162
static int prevCursorY = -1;
163
164
static bool mouseButtonDown = false;
165
static bool hideCursor = false;
166
static bool inResizeMove = false;
167
static bool hasFocus = true;
168
static bool g_keepScreenBright = false;
169
170
static bool disasmMapLoadPending = false;
171
static bool memoryMapLoadPending = false;
172
static bool g_wasMinimized = false;
173
static bool g_inForcedResize = false;
174
175
176
// gross hack
177
bool noFocusPause = false; // TOGGLE_PAUSE state to override pause on lost focus
178
179
static bool trapMouse = true; // Handles some special cases(alt+tab, win menu) when game is running and mouse is confined
180
181
static constexpr const wchar_t *szWindowClass = L"PPSSPPWnd";
182
183
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
184
185
HWND GetHWND() { return hwndMain; }
186
HINSTANCE GetHInstance() { return hInst; }
187
188
void SetKeepScreenBright(bool keepBright) {
189
g_keepScreenBright = keepBright;
190
}
191
192
void Init(HINSTANCE hInstance) {
193
// Register classes - Main Window
194
WNDCLASSEX wcex{};
195
wcex.cbSize = sizeof(WNDCLASSEX);
196
wcex.style = 0; // Show in taskbar
197
wcex.lpfnWndProc = (WNDPROC)WndProc;
198
wcex.hInstance = hInstance;
199
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
200
wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // or NULL?
201
wcex.lpszMenuName = (LPCWSTR)IDR_MENU1;
202
wcex.lpszClassName = szWindowClass;
203
wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_PPSSPP);
204
wcex.hIconSm = (HICON)LoadImage(hInstance, (LPCTSTR)IDI_PPSSPP, IMAGE_ICON, 16, 16, LR_SHARED);
205
RegisterClassEx(&wcex);
206
}
207
208
void SavePosition() {
209
if (g_Config.bFullScreen) {
210
// Don't save the position of the full screen window. We keep the old saved position around.
211
return;
212
}
213
WINDOWPLACEMENT placement{};
214
GetWindowPlacement(hwndMain, &placement);
215
WindowSizeState sizeState = ShowCmdToWindowSizeState(placement.showCmd);
216
if (sizeState == WindowSizeState::Minimized) {
217
// Don't save minimized position.
218
sizeState = WindowSizeState::Normal;
219
}
220
g_Config.iWindowSizeState = (int)sizeState;
221
g_Config.iWindowX = placement.rcNormalPosition.left;
222
g_Config.iWindowY = placement.rcNormalPosition.top;
223
g_Config.iWindowWidth = placement.rcNormalPosition.right - placement.rcNormalPosition.left;
224
g_Config.iWindowHeight = placement.rcNormalPosition.bottom - placement.rcNormalPosition.top;
225
}
226
227
static void GetWindowSizeAtResolution(int xres, int yres, int *windowWidth, int *windowHeight) {
228
RECT rc{};
229
rc.right = xres;
230
rc.bottom = yres;
231
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, TRUE);
232
*windowWidth = rc.right - rc.left;
233
*windowHeight = rc.bottom - rc.top;
234
}
235
236
void SetWindowSize(int zoom) {
237
AssertCurrentThreadName("Main");
238
239
if (g_Config.bFullScreen) {
240
return;
241
}
242
243
// Actually, auto mode should be more granular...
244
int width, height;
245
const DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());
246
if (config.InternalRotationIsPortrait()) {
247
GetWindowSizeAtResolution(272 * (int)zoom, 480 * (int)zoom, &width, &height);
248
} else {
249
GetWindowSizeAtResolution(480 * (int)zoom, 272 * (int)zoom, &width, &height);
250
}
251
g_Config.iWindowWidth = width;
252
g_Config.iWindowHeight = height;
253
MoveWindow(hwndMain, g_Config.iWindowX, g_Config.iWindowY, width, height, TRUE);
254
}
255
256
void SetInternalResolution(int res) {
257
if (res >= 0 && res <= RESOLUTION_MAX)
258
g_Config.iInternalResolution = res;
259
else {
260
if (++g_Config.iInternalResolution > RESOLUTION_MAX)
261
g_Config.iInternalResolution = 0;
262
}
263
264
System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED);
265
}
266
267
void CorrectCursor() {
268
const bool autoHide = ((g_Config.bFullScreen && !mouseButtonDown) || (g_Config.bMouseControl && trapMouse)) && GetUIState() == UISTATE_INGAME;
269
if (autoHide && (hideCursor || g_Config.bMouseControl)) {
270
while (cursorCounter >= 0) {
271
cursorCounter = ShowCursor(FALSE);
272
}
273
if (g_Config.bMouseConfine) {
274
RECT rc;
275
GetClientRect(hwndMain, &rc);
276
ClientToScreen(hwndMain, reinterpret_cast<POINT*>(&rc.left));
277
ClientToScreen(hwndMain, reinterpret_cast<POINT*>(&rc.right));
278
ClipCursor(&rc);
279
}
280
} else {
281
hideCursor = !autoHide;
282
if (cursorCounter < 0) {
283
cursorCounter = ShowCursor(TRUE);
284
SetCursor(LoadCursor(NULL, IDC_ARROW));
285
ClipCursor(NULL);
286
}
287
}
288
}
289
290
static void HandleSizeChange() {
291
Native_NotifyWindowHidden(false);
292
if (!g_Config.bPauseWhenMinimized) {
293
System_PostUIMessage(UIMessage::WINDOW_MINIMIZED, "false");
294
}
295
296
int width, height;
297
W32Util::GetWindowRes(hwndMain, &width, &height);
298
299
// Setting pixelWidth to be too small could have odd consequences.
300
if (width >= 4 && height >= 4) {
301
// The framebuffer manager reads these once per frame, hopefully safe enough.. should really use a mutex or some
302
// much better mechanism.
303
PSP_CoreParameter().pixelWidth = width;
304
PSP_CoreParameter().pixelHeight = height;
305
}
306
307
DEBUG_LOG(Log::System, "Pixel width/height: %dx%d", PSP_CoreParameter().pixelWidth, PSP_CoreParameter().pixelHeight);
308
309
if (Native_UpdateScreenScale(width, height, UIScaleFactorToMultiplier(g_Config.iUIScaleFactor))) {
310
System_PostUIMessage(UIMessage::GPU_DISPLAY_RESIZED);
311
System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED);
312
}
313
}
314
315
void ApplyFullscreenState(HWND hWnd, bool goFullscreen) {
316
GraphicsContext *graphicsContext = PSP_CoreParameter().graphicsContext;
317
// Make sure no rendering is happening during the switch.
318
if (graphicsContext) {
319
graphicsContext->Pause();
320
}
321
322
const DWORD prevStyle = GetWindowLong(hWnd, GWL_STYLE);
323
const bool isCurrentlyFullscreen = !(prevStyle & WS_OVERLAPPEDWINDOW);
324
325
if (goFullscreen && !isCurrentlyFullscreen) {
326
INFO_LOG(Log::System, "ApplyFullscreenState: Entering fullscreen from %s mode at %dx%d+%d+%d",
327
WindowSizeStateToString((WindowSizeState)g_Config.iWindowSizeState),
328
g_Config.iWindowWidth, g_Config.iWindowHeight,
329
g_Config.iWindowX, g_Config.iWindowY);
330
331
// Transitioning to Fullscreen
332
WINDOWPLACEMENT wp = {sizeof(WINDOWPLACEMENT)};
333
if (GetWindowPlacement(hWnd, &wp)) {
334
g_Config.iWindowX = wp.rcNormalPosition.left;
335
g_Config.iWindowY = wp.rcNormalPosition.top;
336
g_Config.iWindowWidth = wp.rcNormalPosition.right - wp.rcNormalPosition.left;
337
g_Config.iWindowHeight = wp.rcNormalPosition.bottom - wp.rcNormalPosition.top;
338
wp.showCmd = SW_SHOW;
339
}
340
341
if (g_Config.bFullScreenMulti) {
342
SetMenu(hWnd, NULL);
343
// Strip all decorations
344
SetWindowLong(hWnd, GWL_STYLE, prevStyle & ~WS_OVERLAPPEDWINDOW);
345
// Maximize isn't enough to display on all monitors.
346
// Remember that negative coordinates may be valid.
347
const int totalX = GetSystemMetrics(SM_XVIRTUALSCREEN);
348
const int totalY = GetSystemMetrics(SM_YVIRTUALSCREEN);
349
const int totalWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
350
const int totalHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
351
SetWindowPos(hWnd, HWND_TOP,
352
totalX, totalY,
353
totalWidth, totalHeight,
354
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
355
} else {
356
MONITORINFO mi = {sizeof(mi)};
357
if (GetMonitorInfo(MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST), &mi)) {
358
SetMenu(hWnd, NULL);
359
// Strip all decorations
360
SetWindowLong(hWnd, GWL_STYLE, prevStyle & ~WS_OVERLAPPEDWINDOW);
361
362
SetWindowPos(hWnd, HWND_TOP,
363
mi.rcMonitor.left, mi.rcMonitor.top,
364
mi.rcMonitor.right - mi.rcMonitor.left,
365
mi.rcMonitor.bottom - mi.rcMonitor.top,
366
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
367
}
368
}
369
} else if (!goFullscreen && isCurrentlyFullscreen) {
370
INFO_LOG(Log::System, "ApplyFullscreenState: Exiting fullscreen to %s mode at %dx%d+%d+%d",
371
WindowSizeStateToString((WindowSizeState)g_Config.iWindowSizeState),
372
g_Config.iWindowWidth, g_Config.iWindowHeight,
373
g_Config.iWindowX, g_Config.iWindowY);
374
375
// Transitioning to Windowed
376
SetWindowLong(hWnd, GWL_STYLE, prevStyle | WS_OVERLAPPEDWINDOW);
377
SetMenu(hWnd, g_hMenu);
378
379
WINDOWPLACEMENT wp = {sizeof(WINDOWPLACEMENT)};
380
wp.showCmd = WindowSizeStateToShowCmd((WindowSizeState)g_Config.iWindowSizeState);
381
wp.rcNormalPosition = {g_Config.iWindowX, g_Config.iWindowY, g_Config.iWindowX + g_Config.iWindowWidth, g_Config.iWindowY + g_Config.iWindowHeight};
382
383
if (wp.showCmd == SW_SHOWMINIMIZED) wp.showCmd = SW_SHOWNORMAL;
384
385
SetWindowPlacement(hWnd, &wp);
386
SetWindowPos(hWnd, NULL, 0, 0, 0, 0,
387
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
388
}
389
390
CorrectCursor();
391
392
ShowOwnedPopups(hwndMain, goFullscreen ? FALSE : TRUE);
393
W32Util::MakeTopMost(hwndMain, g_Config.bTopMost);
394
395
WindowsRawInput::NotifyMenu();
396
397
if (graphicsContext) {
398
graphicsContext->Resume();
399
}
400
}
401
402
void Minimize() {
403
ShowWindow(hwndMain, SW_MINIMIZE);
404
g_InputManager.LoseFocus();
405
}
406
407
// TODO: Currently unused.
408
RECT DetermineDefaultWindowRectangle() {
409
const int virtualScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
410
const int virtualScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
411
const int virtualScreenX = GetSystemMetrics(SM_XVIRTUALSCREEN);
412
const int virtualScreenY = GetSystemMetrics(SM_YVIRTUALSCREEN);
413
const int currentScreenWidth = GetSystemMetrics(SM_CXSCREEN);
414
const int currentScreenHeight = GetSystemMetrics(SM_CYSCREEN);
415
416
bool resetPositionX = true;
417
bool resetPositionY = true;
418
419
if (g_Config.iWindowWidth > 0 && g_Config.iWindowHeight > 0 && !g_Config.bFullScreen) {
420
bool visibleHorizontally = ((g_Config.iWindowX + g_Config.iWindowWidth) >= virtualScreenX) &&
421
((g_Config.iWindowX + g_Config.iWindowWidth) < (virtualScreenWidth + g_Config.iWindowWidth));
422
423
bool visibleVertically = ((g_Config.iWindowY + g_Config.iWindowHeight) >= virtualScreenY) &&
424
((g_Config.iWindowY + g_Config.iWindowHeight) < (virtualScreenHeight + g_Config.iWindowHeight));
425
426
if (visibleHorizontally)
427
resetPositionX = false;
428
if (visibleVertically)
429
resetPositionY = false;
430
}
431
432
// Try to workaround #9563.
433
if (!resetPositionY && g_Config.iWindowY < 0) {
434
g_Config.iWindowY = 0;
435
}
436
437
int windowWidth = g_Config.iWindowWidth;
438
int windowHeight = g_Config.iWindowHeight;
439
440
// First, get the w/h right.
441
if (windowWidth <= 0 || windowHeight <= 0) {
442
DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());
443
const bool portrait = config.InternalRotationIsPortrait();
444
445
// We want to adjust for DPI but still get an integer pixel scaling ratio.
446
double dpi_scale = 96.0 / System_GetPropertyFloat(SYSPROP_DISPLAY_DPI);
447
int scale = (int)ceil(2.0 / dpi_scale);
448
449
GetWindowSizeAtResolution(scale * (portrait ? 272 : 480), scale * (portrait ? 480 : 272), &windowWidth, &windowHeight);
450
}
451
452
// Then center if necessary. One dimension at a time.
453
// Max is to make sure that if we end up making the window bigger than the screen (which is not ideal), the top left
454
// corner, and thus the menu etc, will be visible. Also potential workaround for #9563.
455
int x = g_Config.iWindowX;
456
int y = g_Config.iWindowY;
457
if (resetPositionX) {
458
x = std::max(0, (currentScreenWidth - windowWidth) / 2);
459
}
460
if (resetPositionY) {
461
y = std::max(0, (currentScreenHeight - windowHeight) / 2);
462
}
463
464
RECT rc;
465
rc.left = x;
466
rc.right = rc.left + windowWidth;
467
rc.top = y;
468
rc.bottom = rc.top + windowHeight;
469
return rc;
470
}
471
472
void UpdateWindowTitle() {
473
std::wstring title;
474
{
475
std::lock_guard<std::mutex> lock(g_windowTitleLock);
476
title = g_windowTitle;
477
}
478
if (PPSSPP_ID >= 1 && GetInstancePeerCount() > 1) {
479
title.append(ConvertUTF8ToWString(StringFromFormat(" (instance: %d)", (int)PPSSPP_ID)));
480
}
481
SetWindowText(hwndMain, title.c_str());
482
}
483
484
void SetWindowTitle(const wchar_t *title) {
485
{
486
std::lock_guard<std::mutex> lock(g_windowTitleLock);
487
g_windowTitle = title;
488
}
489
PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_WINDOW_TITLE_CHANGED, 0, 0);
490
}
491
492
BOOL Show(HINSTANCE hInstance) {
493
hInst = hInstance; // Store instance handle in our global variable.
494
495
hwndMain = CreateWindowEx(0, szWindowClass, L"", WS_OVERLAPPEDWINDOW,
496
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
497
498
if (!hwndMain)
499
return FALSE;
500
501
g_inForcedResize = true;
502
503
g_hMenu = GetMenu(hwndMain);
504
505
WINDOWPLACEMENT placement = {sizeof(WINDOWPLACEMENT)};
506
placement.showCmd = WindowSizeStateToShowCmd((WindowSizeState)g_Config.iWindowSizeState);
507
placement.rcNormalPosition.left = g_Config.iWindowX;
508
placement.rcNormalPosition.top = g_Config.iWindowY;
509
placement.rcNormalPosition.right = g_Config.iWindowX + g_Config.iWindowWidth;
510
placement.rcNormalPosition.bottom = g_Config.iWindowY + g_Config.iWindowHeight;
511
SetWindowPlacement(hwndMain, &placement);
512
513
// SetWindowLong(hwndMain, GWL_EXSTYLE, WS_EX_APPWINDOW);
514
515
const DWM_WINDOW_CORNER_PREFERENCE pref = DWMWCP_DONOTROUND;
516
DwmSetWindowAttribute(hwndMain, DWMWA_WINDOW_CORNER_PREFERENCE, &pref, sizeof(pref));
517
518
MainMenuInit(hwndMain, g_hMenu);
519
520
// Accept dragged files.
521
DragAcceptFiles(hwndMain, TRUE);
522
523
hideCursor = true;
524
SetTimer(hwndMain, TIMER_CURSORUPDATE, CURSORUPDATE_INTERVAL_MS, 0);
525
526
ApplyFullscreenState(hwndMain, g_Config.bFullScreen);
527
528
W32Util::MakeTopMost(hwndMain, g_Config.bTopMost);
529
530
touchHandler.registerTouchWindow(hwndMain);
531
532
WindowsRawInput::Init();
533
534
UpdateWindow(hwndMain);
535
536
SetFocus(hwndMain);
537
538
g_inForcedResize = false;
539
540
return TRUE;
541
}
542
543
void CreateDisasmWindow() {
544
if (!disasmWindow) {
545
disasmWindow = new CDisasm(MainWindow::GetHInstance(), MainWindow::GetHWND(), currentDebugMIPS);
546
DialogManager::AddDlg(disasmWindow);
547
}
548
if (disasmMapLoadPending) {
549
disasmWindow->NotifyMapLoaded();
550
disasmMapLoadPending = false;
551
}
552
}
553
554
void CreateGeDebuggerWindow() {
555
#if PPSSPP_API(ANY_GL)
556
if (!geDebuggerWindow) {
557
geDebuggerWindow = new CGEDebugger(MainWindow::GetHInstance(), MainWindow::GetHWND());
558
DialogManager::AddDlg(geDebuggerWindow);
559
}
560
#endif
561
}
562
563
void CreateMemoryWindow() {
564
if (!memoryWindow) {
565
memoryWindow = new CMemoryDlg(MainWindow::GetHInstance(), MainWindow::GetHWND(), currentDebugMIPS);
566
DialogManager::AddDlg(memoryWindow);
567
}
568
if (memoryMapLoadPending) {
569
memoryWindow->NotifyMapLoaded();
570
memoryMapLoadPending = false;
571
}
572
}
573
574
void CreateVFPUWindow() {
575
if (!vfpudlg) {
576
vfpudlg = new CVFPUDlg(MainWindow::GetHInstance(), MainWindow::GetHWND(), currentDebugMIPS);
577
DialogManager::AddDlg(vfpudlg);
578
}
579
}
580
581
void NotifyDebuggerMapLoaded() {
582
disasmMapLoadPending = disasmWindow == nullptr;
583
memoryMapLoadPending = memoryWindow == nullptr;
584
if (!disasmMapLoadPending)
585
disasmWindow->NotifyMapLoaded();
586
if (!memoryMapLoadPending)
587
memoryWindow->NotifyMapLoaded();
588
}
589
590
void DestroyDebugWindows() {
591
DialogManager::RemoveDlg(disasmWindow);
592
delete disasmWindow;
593
disasmWindow = nullptr;
594
595
#if PPSSPP_API(ANY_GL)
596
DialogManager::RemoveDlg(geDebuggerWindow);
597
delete geDebuggerWindow;
598
geDebuggerWindow = nullptr;
599
#endif
600
601
DialogManager::RemoveDlg(memoryWindow);
602
delete memoryWindow;
603
memoryWindow = nullptr;
604
605
DialogManager::RemoveDlg(vfpudlg);
606
delete vfpudlg;
607
vfpudlg = nullptr;
608
}
609
610
bool ConfirmAction(HWND hWnd, bool actionIsReset) {
611
const GlobalUIState state = GetUIState();
612
if (state == UISTATE_MENU || state == UISTATE_EXIT) {
613
return true;
614
}
615
616
std::string confirmExitMessage = GetConfirmExitMessage();
617
if (confirmExitMessage.empty()) {
618
return true;
619
}
620
auto di = GetI18NCategory(I18NCat::DIALOG);
621
auto mm = GetI18NCategory(I18NCat::MAINMENU);
622
if (!actionIsReset) {
623
confirmExitMessage += '\n';
624
confirmExitMessage += di->T("Are you sure you want to exit?");
625
} else {
626
// Reset is bit rarer, let's just omit the extra message for now.
627
}
628
return IDYES == MessageBox(hWnd, ConvertUTF8ToWString(confirmExitMessage).c_str(), ConvertUTF8ToWString(mm->T("Exit")).c_str(), MB_YESNO | MB_ICONQUESTION);
629
}
630
631
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
632
static bool firstErase = true;
633
LRESULT darkResult = 0;
634
if (UAHDarkModeWndProc(hWnd, message, wParam, lParam, &darkResult)) {
635
return darkResult;
636
}
637
638
static bool first = true;
639
switch (message) {
640
case WM_CREATE:
641
first = true;
642
if (!IsVistaOrHigher()) {
643
// Remove the D3D11 choice on versions below XP
644
RemoveMenu(GetMenu(hWnd), ID_OPTIONS_DIRECT3D11, MF_BYCOMMAND);
645
}
646
if (g_darkModeSupported) {
647
SendMessageW(hWnd, WM_THEMECHANGED, 0, 0);
648
}
649
SetAssertDialogParent(hWnd);
650
break;
651
652
case WM_USER_RUN_CALLBACK:
653
{
654
auto callback = reinterpret_cast<void (*)(void *window, void *userdata)>(wParam);
655
void *userdata = reinterpret_cast<void *>(lParam);
656
callback(hWnd, userdata);
657
break;
658
}
659
case WM_USER_GET_BASE_POINTER:
660
Reporting::NotifyDebugger();
661
switch (lParam) {
662
case 0: return (u32)(u64)Memory::base;
663
case 1: return (u32)((u64)Memory::base >> 32);
664
case 2: return (u32)(u64)(&Memory::base);
665
case 3: return (u32)((u64)(&Memory::base) >> 32);
666
default:
667
return 0;
668
}
669
break;
670
671
case WM_USER_GET_EMULATION_STATE:
672
return (u32)(Core_IsActive() && GetUIState() == UISTATE_INGAME);
673
674
case WM_USER_GET_CURRENT_GAMEID:
675
{
676
// Return game ID as four u32 values
677
// wParam: 0-3 = which u32 to return (chars 0-3, 4-7, 8-11, 12-15)
678
// Returns: packed u32 with 4 bytes of game ID
679
if (!PSP_IsInited()) {
680
return 0;
681
}
682
const std::string gameID = Reporting::CurrentGameID();
683
if (gameID.empty()) {
684
return 0;
685
}
686
const size_t offset = (wParam & 0x3) * 4; // 0, 4, 8, 12
687
u32 packed = 0;
688
for (size_t i = 0; i < 4; ++i) {
689
if (offset + i < gameID.length()) {
690
const u8 c = static_cast<u8>(gameID[offset + i]);
691
packed |= ((u32)c << (i * 8));
692
}
693
}
694
return packed;
695
}
696
case WM_USER_GET_MODULE_INFO:
697
{
698
// Get module information by name
699
// wParam: pointer to module name (null-terminated string)
700
// lParam: 0 = address, 1 = size, 2 = active flag
701
// Returns: u64 packed with module info, or 0 if not found
702
if (!PSP_IsInited() || !g_symbolMap)
703
{
704
return 0;
705
}
706
const char* moduleName = reinterpret_cast<const char*>(wParam);
707
if (!moduleName)
708
{
709
return 0;
710
}
711
// Get all modules from symbol map
712
auto modules = g_symbolMap->getAllModules();
713
for (const auto& module : modules)
714
{
715
if (module.name == moduleName)
716
{
717
switch (lParam)
718
{
719
case 0:
720
// Return address as u32 (low 32 bits)
721
return (u64)module.address;
722
case 1:
723
// Return size as u32 (low 32 bits)
724
return (u64)module.size;
725
case 2:
726
// Return active flag in bit 0, padded with zeros
727
return (u64)(module.active ? 1 : 0);
728
case 3:
729
// Return all info packed: address (bits 0-31), size (bits 32-62), active (bit 63)
730
return ((u64)module.address) | (((u64)module.size) << 32) | (module.active ? (1ULL << 63) : 0);
731
default:
732
return 0;
733
}
734
}
735
}
736
// Module not found
737
return 0;
738
}
739
740
// Hack to kill the white line underneath the menubar.
741
// From https://stackoverflow.com/questions/57177310/how-to-paint-over-white-line-between-menu-bar-and-client-area-of-window
742
case WM_NCPAINT:
743
case WM_NCACTIVATE:
744
{
745
if (!IsDarkModeEnabled() || IsIconic(hWnd)) {
746
return DefWindowProc(hWnd, message, wParam, lParam);
747
}
748
749
auto result = DefWindowProc(hWnd, message, wParam, lParam);
750
// Paint over the line with pure black. Could also try to figure out the dark theme color.
751
HDC hdc = GetWindowDC(hWnd);
752
RECT r = W32Util::GetNonclientMenuBorderRect(hWnd);
753
HBRUSH red = CreateSolidBrush(RGB(0, 0, 0));
754
FillRect(hdc, &r, red);
755
DeleteObject(red);
756
ReleaseDC(hWnd, hdc);
757
return result;
758
}
759
760
case WM_GETMINMAXINFO:
761
{
762
MINMAXINFO *minmax = reinterpret_cast<MINMAXINFO *>(lParam);
763
RECT rc = { 0 };
764
const DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());
765
bool portrait = config.InternalRotationIsPortrait();
766
rc.right = portrait ? 272 : 480;
767
rc.bottom = portrait ? 480 : 272;
768
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, TRUE);
769
minmax->ptMinTrackSize.x = rc.right - rc.left;
770
minmax->ptMinTrackSize.y = rc.bottom - rc.top;
771
return 0;
772
}
773
774
case WM_ACTIVATE:
775
{
776
UpdateWindowTitle();
777
bool pause = true;
778
if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) {
779
WindowsRawInput::GainFocus();
780
if (!IsIconic(GetHWND())) {
781
g_InputManager.GainFocus();
782
}
783
g_activeWindow = WINDOW_MAINWINDOW;
784
pause = false;
785
} else {
786
g_activeWindow = WINDOW_OTHER;
787
}
788
789
if (!noFocusPause && g_Config.bPauseOnLostFocus && GetUIState() == UISTATE_INGAME) {
790
if (pause != Core_IsStepping()) {
791
if (disasmWindow) {
792
SendMessage(disasmWindow->GetDlgHandle(), WM_COMMAND, IDC_STOPGO, 0);
793
} else {
794
if (pause) {
795
Core_Break(BreakReason::UIFocus, 0);
796
} else if (Core_BreakReason() == BreakReason::UIFocus) {
797
Core_Resume();
798
}
799
}
800
}
801
}
802
803
if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) {
804
System_PostUIMessage(UIMessage::GOT_FOCUS);
805
hasFocus = true;
806
trapMouse = true;
807
}
808
if (wParam == WA_INACTIVE) {
809
System_PostUIMessage(UIMessage::LOST_FOCUS);
810
WindowsRawInput::LoseFocus();
811
g_InputManager.LoseFocus();
812
hasFocus = false;
813
trapMouse = false;
814
}
815
}
816
break;
817
818
case WM_SETFOCUS:
819
UpdateWindowTitle();
820
break;
821
822
case WM_ERASEBKGND:
823
if (firstErase) {
824
firstErase = false;
825
// Paint black on first erase while OpenGL stuff is loading
826
return DefWindowProc(hWnd, message, wParam, lParam);
827
}
828
// Then never erase, let the OpenGL drawing take care of everything.
829
return 1;
830
831
case WM_USER_APPLY_FULLSCREEN:
832
ApplyFullscreenState(hwndMain, g_Config.bFullScreen);
833
break;
834
835
case WM_DISPLAYCHANGE:
836
// If resolution changes while we are fullscreen, re-snap to the new monitor size
837
if (g_Config.bFullScreen) {
838
MONITORINFO mi = {sizeof(mi)};
839
if (GetMonitorInfo(MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST), &mi)) {
840
SetWindowPos(hWnd, HWND_TOP,
841
mi.rcMonitor.left, mi.rcMonitor.top,
842
mi.rcMonitor.right - mi.rcMonitor.left,
843
mi.rcMonitor.bottom - mi.rcMonitor.top,
844
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
845
}
846
}
847
break;
848
849
case WM_WINDOWPOSCHANGED:
850
{
851
// Handling this means that WM_SIZE and WM_MOVE won't be sent, except once during
852
// window creation for some reason.
853
const WINDOWPOS *pos = reinterpret_cast<WINDOWPOS*>(lParam);
854
if (!pos) {
855
// Uh?
856
return DefWindowProc(hWnd, message, wParam, lParam);
857
}
858
const bool sizeChanged = !(pos->flags & SWP_NOSIZE);
859
860
WINDOWPLACEMENT wp{sizeof(wp)};
861
GetWindowPlacement(hWnd, &wp);
862
if (!g_Config.bFullScreen) {
863
g_Config.iWindowSizeState = (int)ShowCmdToWindowSizeState(wp.showCmd);
864
}
865
866
switch (wp.showCmd) {
867
case SW_SHOWNORMAL:
868
case SW_SHOWMAXIMIZED:
869
if (hasFocus) {
870
g_InputManager.GainFocus();
871
}
872
if (g_wasMinimized) {
873
System_PostUIMessage(UIMessage::WINDOW_RESTORED, "true");
874
g_wasMinimized = false;
875
}
876
break;
877
case SW_SHOWMINIMIZED:
878
Native_NotifyWindowHidden(true);
879
if (!g_Config.bPauseWhenMinimized) {
880
System_PostUIMessage(UIMessage::WINDOW_MINIMIZED, "true");
881
}
882
g_InputManager.LoseFocus();
883
g_wasMinimized = true;
884
break;
885
default:
886
break;
887
}
888
889
if (sizeChanged) {
890
// Check that we're not in a resize that we ourselves are performing.
891
if (g_Config.bFullScreen && !g_inForcedResize) {
892
MONITORINFO mi = {sizeof(mi)};
893
if (GetMonitorInfo(MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST), &mi)) {
894
int monWidth = mi.rcMonitor.right - mi.rcMonitor.left;
895
int monHeight = mi.rcMonitor.bottom - mi.rcMonitor.top;
896
897
// If the new size is no longer the full monitor size, drop FS state (put back the menu
898
// and recalculate window decorations without actually changing the size of the window).
899
if (pos->cx != monWidth || pos->cy != monHeight) {
900
g_Config.bFullScreen = false;
901
if (GetMenu(hWnd) == NULL) {
902
SetMenu(hWnd, g_hMenu);
903
}
904
DWORD style = GetWindowLong(hWnd, GWL_STYLE);
905
if (!(style & WS_CAPTION)) {
906
SetWindowLong(hWnd, GWL_STYLE, style | WS_OVERLAPPEDWINDOW);
907
SetWindowPos(hWnd, NULL, 0, 0, 0, 0,
908
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE);
909
}
910
}
911
}
912
}
913
if (!inResizeMove) {
914
HandleSizeChange();
915
}
916
}
917
return 0;
918
}
919
920
case WM_ENTERSIZEMOVE:
921
inResizeMove = true;
922
break;
923
924
case WM_EXITSIZEMOVE:
925
inResizeMove = false;
926
HandleSizeChange();
927
break;
928
929
// Wheel events have to stay in WndProc for compatibility with older Windows(7). See #12156
930
case WM_MOUSEWHEEL:
931
{
932
int wheelDelta = (short)(wParam >> 16);
933
KeyInput key;
934
key.deviceId = DEVICE_ID_MOUSE;
935
936
if (wheelDelta < 0) {
937
key.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN;
938
wheelDelta = -wheelDelta;
939
} else {
940
key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP;
941
}
942
// There's no release event, but we simulate it in NativeKey/NativeFrame.
943
key.flags = (KeyInputFlags)((u32)KeyInputFlags::DOWN | (u32)KeyInputFlags::HAS_WHEEL_DELTA | (wheelDelta << 16));
944
NativeKey(key);
945
}
946
break;
947
948
case WM_TIMER:
949
// Hack: Take the opportunity to also show/hide the mouse cursor in fullscreen mode.
950
switch (wParam) {
951
case TIMER_CURSORUPDATE:
952
CorrectCursor();
953
return 0;
954
955
case TIMER_CURSORMOVEUPDATE:
956
hideCursor = true;
957
KillTimer(hWnd, TIMER_CURSORMOVEUPDATE);
958
return 0;
959
960
default:
961
break;
962
}
963
break;
964
965
case WM_COMMAND:
966
{
967
if (!MainThread_Ready())
968
return DefWindowProc(hWnd, message, wParam, lParam);
969
970
MainWindowMenu_Process(hWnd, wParam);
971
}
972
break;
973
974
case WM_INPUT:
975
return WindowsRawInput::Process(hWnd, wParam, lParam);
976
977
// TODO: Could do something useful with WM_INPUT_DEVICE_CHANGE?
978
979
// Not sure why we are actually getting WM_CHAR even though we use RawInput, but alright..
980
case WM_CHAR:
981
return WindowsRawInput::ProcessChar(hWnd, wParam, lParam);
982
983
case WM_DEVICECHANGE:
984
#ifndef _M_ARM
985
DinputDevice::CheckDevices();
986
#endif
987
if (winCamera)
988
winCamera->CheckDevices();
989
if (winMic)
990
winMic->CheckDevices();
991
return DefWindowProc(hWnd, message, wParam, lParam);
992
993
case WM_VERYSLEEPY_MSG:
994
switch (wParam) {
995
case VERYSLEEPY_WPARAM_SUPPORTED:
996
return TRUE;
997
998
case VERYSLEEPY_WPARAM_GETADDRINFO:
999
{
1000
VerySleepy_AddrInfo *info = (VerySleepy_AddrInfo *)lParam;
1001
const u8 *ptr = (const u8 *)info->addr;
1002
std::string name;
1003
1004
std::lock_guard<std::recursive_mutex> guard(MIPSComp::jitLock);
1005
if (MIPSComp::jit && MIPSComp::jit->DescribeCodePtr(ptr, name)) {
1006
swprintf_s(info->name, L"Jit::%S", name.c_str());
1007
return TRUE;
1008
}
1009
if (gpu && gpu->DescribeCodePtr(ptr, name)) {
1010
swprintf_s(info->name, L"GPU::%S", name.c_str());
1011
return TRUE;
1012
}
1013
}
1014
return FALSE;
1015
1016
default:
1017
return FALSE;
1018
}
1019
break;
1020
1021
case WM_DROPFILES:
1022
{
1023
if (!MainThread_Ready()) {
1024
return DefWindowProc(hWnd, message, wParam, lParam);
1025
}
1026
const HDROP hdrop = (HDROP)wParam;
1027
const int count = DragQueryFile(hdrop, 0xFFFFFFFF, 0, 0);
1028
if (count != 1) {
1029
// TODO: Translate? Or just not bother?
1030
MessageBox(hwndMain, L"You can only load one file at a time", L"Error", MB_ICONINFORMATION);
1031
} else {
1032
wchar_t filename[1024];
1033
if (DragQueryFile(hdrop, 0, filename, ARRAY_SIZE(filename)) != 0) {
1034
const std::string utf8_filename = ReplaceAll(ConvertWStringToUTF8(filename), "\\", "/");
1035
System_PostUIMessage(UIMessage::REQUEST_GAME_BOOT, utf8_filename);
1036
}
1037
}
1038
DragFinish(hdrop);
1039
}
1040
break;
1041
1042
case WM_CLOSE:
1043
{
1044
if (ConfirmAction(hWnd, false)) {
1045
DestroyWindow(hWnd);
1046
}
1047
return 0;
1048
}
1049
1050
case WM_DESTROY:
1051
SavePosition();
1052
g_InputManager.StopPolling();
1053
g_InputManager.Shutdown();
1054
WindowsRawInput::Shutdown();
1055
1056
MainThread_Stop();
1057
KillTimer(hWnd, TIMER_CURSORUPDATE);
1058
KillTimer(hWnd, TIMER_CURSORMOVEUPDATE);
1059
// Main window is gone, this tells the message loop to exit.
1060
PostQuitMessage(0);
1061
return 0;
1062
1063
case WM_USER + 1:
1064
NotifyDebuggerMapLoaded();
1065
if (disasmWindow)
1066
disasmWindow->UpdateDialog();
1067
break;
1068
1069
case WM_USER_SAVESTATE_FINISH:
1070
SetCursor(LoadCursor(0, IDC_ARROW));
1071
break;
1072
1073
case WM_USER_UPDATE_UI:
1074
TranslateMenus(hwndMain, g_hMenu);
1075
// Update checked status immediately for accelerators.
1076
UpdateMenus(nullptr);
1077
break;
1078
1079
case WM_USER_WINDOW_TITLE_CHANGED:
1080
UpdateWindowTitle();
1081
break;
1082
1083
case WM_USER_RESTART_EMUTHREAD:
1084
NativeSetRestarting();
1085
g_InputManager.StopPolling();
1086
MainThread_Stop();
1087
UpdateUIState(UISTATE_MENU);
1088
MainThread_Start(g_Config.iGPUBackend == (int)GPUBackend::OPENGL);
1089
g_InputManager.BeginPolling();
1090
break;
1091
1092
case WM_USER_SWITCHUMD_UPDATED:
1093
UpdateSwitchUMD();
1094
break;
1095
1096
case WM_USER_DESTROY:
1097
DestroyWindow(hWnd);
1098
break;
1099
1100
case WM_INITMENUPOPUP:
1101
// Called when a menu or submenu is about to be opened.
1102
UpdateMenus((HMENU)wParam);
1103
WindowsRawInput::NotifyMenu();
1104
trapMouse = false;
1105
break;
1106
1107
case WM_EXITMENULOOP:
1108
// Called when menu is closed.
1109
trapMouse = true;
1110
break;
1111
1112
// Turn off the screensaver if in-game.
1113
// Note that if there's a screensaver password, this simple method
1114
// doesn't work on Vista or higher.
1115
case WM_SYSCOMMAND:
1116
// Disable Alt key for menu if it's been mapped.
1117
if (wParam == SC_KEYMENU && (lParam >> 16) <= 0) {
1118
if (KeyMap::IsKeyMapped(DEVICE_ID_KEYBOARD, NKCODE_ALT_LEFT) || KeyMap::IsKeyMapped(DEVICE_ID_KEYBOARD, NKCODE_ALT_RIGHT)) {
1119
return 0;
1120
}
1121
}
1122
if (g_keepScreenBright) {
1123
switch (wParam) {
1124
case SC_SCREENSAVE:
1125
return 0;
1126
case SC_MONITORPOWER:
1127
if (lParam == 1 || lParam == 2) {
1128
return 0;
1129
} else {
1130
break;
1131
}
1132
default:
1133
// fall down to DefWindowProc
1134
break;
1135
}
1136
}
1137
return DefWindowProc(hWnd, message, wParam, lParam);
1138
case WM_SETTINGCHANGE:
1139
if (g_darkModeSupported && IsColorSchemeChangeMessage(lParam)) {
1140
SendMessageW(hWnd, WM_THEMECHANGED, 0, 0);
1141
}
1142
return DefWindowProc(hWnd, message, wParam, lParam);
1143
1144
case WM_THEMECHANGED:
1145
{
1146
if (g_darkModeSupported) {
1147
_AllowDarkModeForWindow(hWnd, g_darkModeEnabled);
1148
RefreshTitleBarThemeColor(hWnd);
1149
}
1150
return DefWindowProc(hWnd, message, wParam, lParam);
1151
}
1152
1153
case WM_SETCURSOR:
1154
if ((lParam & 0xFFFF) == HTCLIENT && g_Config.bShowImDebugger) {
1155
LPTSTR win32_cursor = 0;
1156
if (g_Config.bShowImDebugger) {
1157
switch (ImGui_ImplPlatform_GetCursor()) {
1158
case ImGuiMouseCursor_Arrow: win32_cursor = IDC_ARROW; break;
1159
case ImGuiMouseCursor_TextInput: win32_cursor = IDC_IBEAM; break;
1160
case ImGuiMouseCursor_ResizeAll: win32_cursor = IDC_SIZEALL; break;
1161
case ImGuiMouseCursor_ResizeEW: win32_cursor = IDC_SIZEWE; break;
1162
case ImGuiMouseCursor_ResizeNS: win32_cursor = IDC_SIZENS; break;
1163
case ImGuiMouseCursor_ResizeNESW: win32_cursor = IDC_SIZENESW; break;
1164
case ImGuiMouseCursor_ResizeNWSE: win32_cursor = IDC_SIZENWSE; break;
1165
case ImGuiMouseCursor_Hand: win32_cursor = IDC_HAND; break;
1166
case ImGuiMouseCursor_NotAllowed: win32_cursor = IDC_NO; break;
1167
default: break;
1168
}
1169
}
1170
SetCursor(win32_cursor ? ::LoadCursor(nullptr, win32_cursor) : nullptr);
1171
return TRUE;
1172
} else {
1173
return DefWindowProc(hWnd, message, wParam, lParam);
1174
}
1175
break;
1176
1177
// Mouse input. We send asynchronous touch events for minimal latency.
1178
case WM_LBUTTONDOWN:
1179
if (!touchHandler.hasTouch() ||
1180
(GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN)
1181
{
1182
// Hack: Take the opportunity to show the cursor.
1183
mouseButtonDown = true;
1184
1185
const float x = GET_X_LPARAM(lParam) * g_display.dpi_scale_x;
1186
const float y = GET_Y_LPARAM(lParam) * g_display.dpi_scale_y;
1187
WindowsRawInput::SetMousePos(x, y);
1188
1189
TouchInput touch{};
1190
touch.flags = TouchInputFlags::DOWN | TouchInputFlags::MOUSE;
1191
touch.buttons = 1;
1192
touch.x = x;
1193
touch.y = y;
1194
NativeTouch(touch);
1195
SetCapture(hWnd);
1196
1197
// Simulate doubleclick, doesn't work with RawInput enabled
1198
static double lastMouseDownTime;
1199
static float lastMouseDownX = -1.0f;
1200
static float lastMouseDownY = -1.0f;
1201
const double now = time_now_d();
1202
if ((now - lastMouseDownTime) < 0.001 * GetDoubleClickTime()) {
1203
const float dx = lastMouseDownX - x;
1204
const float dy = lastMouseDownY - y;
1205
const float distSq = dx * dx + dy * dy;
1206
if (distSq < 3.0f*3.0f && !g_Config.bShowTouchControls && !g_Config.bShowImDebugger && !g_Config.bMouseControl && GetUIState() == UISTATE_INGAME && g_Config.bFullscreenOnDoubleclick) {
1207
g_Config.bFullScreen = !g_Config.bFullScreen;
1208
SendApplyFullscreenState();
1209
}
1210
lastMouseDownTime = 0.0;
1211
} else {
1212
lastMouseDownTime = now;
1213
}
1214
lastMouseDownX = x;
1215
lastMouseDownY = y;
1216
}
1217
break;
1218
1219
case WM_MOUSEMOVE:
1220
if (!touchHandler.hasTouch() ||
1221
(GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN)
1222
{
1223
// Hack: Take the opportunity to show the cursor.
1224
mouseButtonDown = (wParam & MK_LBUTTON) != 0;
1225
int cursorX = GET_X_LPARAM(lParam);
1226
int cursorY = GET_Y_LPARAM(lParam);
1227
// Require at least 2 pixels of movement to reset the hide timer.
1228
if (abs(cursorX - prevCursorX) > 1 || abs(cursorY - prevCursorY) > 1) {
1229
hideCursor = false;
1230
SetTimer(hwndMain, TIMER_CURSORMOVEUPDATE, CURSORUPDATE_MOVE_TIMESPAN_MS, 0);
1231
}
1232
prevCursorX = cursorX;
1233
prevCursorY = cursorY;
1234
1235
const float x = (float)cursorX * g_display.dpi_scale_x;
1236
const float y = (float)cursorY * g_display.dpi_scale_y;
1237
WindowsRawInput::SetMousePos(x, y);
1238
1239
// Mouse moves now happen also when no button is pressed.
1240
TouchInput touch{};
1241
touch.flags = TouchInputFlags::MOVE | TouchInputFlags::MOUSE;
1242
if (wParam & MK_LBUTTON) {
1243
touch.buttons |= 1;
1244
}
1245
if (wParam & MK_RBUTTON) {
1246
touch.buttons |= 2;
1247
}
1248
touch.x = x;
1249
touch.y = y;
1250
NativeTouch(touch);
1251
}
1252
break;
1253
1254
case WM_LBUTTONUP:
1255
if (!touchHandler.hasTouch() ||
1256
(GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN)
1257
{
1258
// Hack: Take the opportunity to hide the cursor.
1259
mouseButtonDown = false;
1260
1261
const float x = (float)GET_X_LPARAM(lParam) * g_display.dpi_scale_x;
1262
const float y = (float)GET_Y_LPARAM(lParam) * g_display.dpi_scale_y;
1263
WindowsRawInput::SetMousePos(x, y);
1264
1265
TouchInput touch{};
1266
touch.buttons = 1;
1267
touch.flags = TouchInputFlags::UP | TouchInputFlags::MOUSE;
1268
touch.x = x;
1269
touch.y = y;
1270
NativeTouch(touch);
1271
ReleaseCapture();
1272
}
1273
break;
1274
1275
case WM_TOUCH:
1276
touchHandler.handleTouchEvent(hWnd, message, wParam, lParam);
1277
return 0;
1278
1279
case WM_RBUTTONDOWN:
1280
{
1281
TouchInput touch{};
1282
touch.buttons = 2;
1283
touch.flags = TouchInputFlags::DOWN | TouchInputFlags::MOUSE;
1284
touch.x = GET_X_LPARAM(lParam) * g_display.dpi_scale_x;
1285
touch.y = GET_Y_LPARAM(lParam) * g_display.dpi_scale_y;
1286
NativeTouch(touch);
1287
break;
1288
}
1289
1290
case WM_RBUTTONUP:
1291
{
1292
TouchInput touch{};
1293
touch.buttons = 2;
1294
touch.flags = TouchInputFlags::UP | TouchInputFlags::MOUSE;
1295
touch.x = GET_X_LPARAM(lParam) * g_display.dpi_scale_x;
1296
touch.y = GET_Y_LPARAM(lParam) * g_display.dpi_scale_y;
1297
NativeTouch(touch);
1298
break;
1299
}
1300
1301
default:
1302
return DefWindowProc(hWnd, message, wParam, lParam);
1303
}
1304
return 0;
1305
}
1306
1307
void ToggleDebugConsoleVisibility() {
1308
if (!g_Config.bEnableLogging) {
1309
g_logManager.GetConsoleListener()->Show(false);
1310
EnableMenuItem(g_hMenu, ID_DEBUG_LOG, MF_GRAYED);
1311
}
1312
else {
1313
g_logManager.GetConsoleListener()->Show(true);
1314
EnableMenuItem(g_hMenu, ID_DEBUG_LOG, MF_ENABLED);
1315
}
1316
}
1317
1318
void SendApplyFullscreenState() {
1319
PostMessage(hwndMain, WM_USER_APPLY_FULLSCREEN, 0, 0);
1320
}
1321
1322
void RunCallbackInWndProc(void (*callback)(void *, void *), void *userdata) {
1323
PostMessage(hwndMain, WM_USER_RUN_CALLBACK, reinterpret_cast<WPARAM>(callback), reinterpret_cast<LPARAM>(userdata));
1324
}
1325
1326
} // namespace MainWindow
1327
1328