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