Files
ppsspp/Windows/MainWindow.cpp
T
Henrik Rydgård 87c0146eaa Fix main menu init
Fixes #21773
2026-06-03 16:00:51 +02:00

1344 lines
41 KiB
C++

// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
// TODO: Get rid of the internal window.
// Tried before but Intel drivers screw up when minimizing, or something ?
#include "stdafx.h"
#include "ppsspp_config.h"
#include "Common/CommonWindows.h"
#include "Common/OSVersion.h"
#include <Windowsx.h>
#include <shellapi.h> // For drag/drop functionality
#include <string>
#include <dwmapi.h>
#include "Common/System/Display.h"
#include "Common/System/NativeApp.h"
#include "Common/System/System.h"
#include "Common/TimeUtil.h"
#include "Common/StringUtils.h"
#include "Common/Data/Text/I18n.h"
#include "Common/Input/InputState.h"
#include "Common/Input/KeyCodes.h"
#include "Common/Thread/ThreadUtil.h"
#include "Common/Data/Encoding/Utf8.h"
#include "ext/imgui/imgui_impl_platform.h"
#include "Core/Core.h"
#include "Core/Config.h"
#include "Core/ConfigValues.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/Instance.h"
#include "Core/KeyMap.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/Reporting.h"
#include "Windows/InputDevice.h"
#if PPSSPP_API(ANY_GL)
#include "Windows/GEDebugger/GEDebugger.h"
#endif
#include "Windows/W32Util/DarkMode.h"
#include "Windows/W32Util/UAHMenuBar.h"
#include "Windows/Debugger/Debugger_Disasm.h"
#include "Windows/Debugger/Debugger_MemoryDlg.h"
#include "Common/GraphicsContext.h"
#include "Windows/main.h"
#ifndef _M_ARM
#include "Windows/DinputDevice.h"
#endif
#include "Windows/EmuThread.h"
#include "Windows/resource.h"
#include "Windows/MainWindow.h"
#include "Common/Log/LogManager.h"
#include "Common/Log/ConsoleListener.h"
#include "Windows/W32Util/DialogManager.h"
#include "Windows/W32Util/Misc.h"
#include "Windows/RawInput.h"
#include "Windows/CaptureDevice.h"
#include "Windows/TouchInputHandler.h"
#include "Windows/MainWindowMenu.h"
#include "GPU/GPUCommon.h"
#include "UI/PauseScreen.h"
#define MOUSEEVENTF_FROMTOUCH_NOPEN 0xFF515780 //http://msdn.microsoft.com/en-us/library/windows/desktop/ms703320(v=vs.85).aspx
#define MOUSEEVENTF_MASK_PLUS_PENTOUCH 0xFFFFFF80
// See https://github.com/unknownbrackets/verysleepy/commit/fc1b1b3bd6081fae3566cdb542d896e413238b71
int verysleepy__useSendMessage = 1;
const UINT WM_VERYSLEEPY_MSG = WM_APP + 0x3117;
const UINT WM_USER_GET_BASE_POINTER = WM_APP + 0x3118; // 0xB118
const UINT WM_USER_GET_EMULATION_STATE = WM_APP + 0x3119; // 0xB119
const UINT WM_USER_GET_CURRENT_GAMEID = WM_APP + 0x311A; // 0xB11A
const UINT WM_USER_GET_MODULE_INFO = WM_APP + 0x311B; // 0xB11B
// Respond TRUE to a message with this param value to indicate support.
const WPARAM VERYSLEEPY_WPARAM_SUPPORTED = 0;
// Respond TRUE to a message wit this param value after filling in the addr name.
const WPARAM VERYSLEEPY_WPARAM_GETADDRINFO = 1;
struct VerySleepy_AddrInfo {
// Always zero for now.
int flags;
// This is the pointer (always passed as 64 bits.)
unsigned long long addr;
// Write the name here.
wchar_t name[256];
};
static std::mutex g_windowTitleLock;
static std::wstring g_windowTitle;
#define TIMER_CURSORUPDATE 1
#define TIMER_CURSORMOVEUPDATE 2
#define CURSORUPDATE_INTERVAL_MS 1000
#define CURSORUPDATE_MOVE_TIMESPAN_MS 500
inline WindowSizeState ShowCmdToWindowSizeState(const int showCmd) {
switch (showCmd) {
case SW_SHOWMAXIMIZED:
return WindowSizeState::Maximized;
case SW_SHOWMINIMIZED:
return WindowSizeState::Minimized;
case SW_SHOWNORMAL:
default:
return WindowSizeState::Normal;
}
}
inline int WindowSizeStateToShowCmd(const WindowSizeState windowSizeState) {
switch (windowSizeState) {
case WindowSizeState::Maximized:
return SW_SHOWMAXIMIZED;
case WindowSizeState::Minimized:
return SW_SHOWMINIMIZED;
default:
return SW_SHOWNORMAL;
}
}
static const char *WindowSizeStateToString(const WindowSizeState state) {
switch (state) {
case WindowSizeState::Normal:
return "Normal";
case WindowSizeState::Minimized:
return "Minimized";
case WindowSizeState::Maximized:
return "Maximized";
default:
return "Unknown";
}
}
namespace MainWindow {
static HWND hwndMain;
static TouchInputHandler touchHandler;
static HMENU g_hMenu;
HINSTANCE hInst;
static int cursorCounter = 0;
static int prevCursorX = -1;
static int prevCursorY = -1;
static bool mouseButtonDown = false;
static bool hideCursor = false;
static bool inResizeMove = false;
static bool hasFocus = true;
static bool g_keepScreenBright = false;
static bool disasmMapLoadPending = false;
static bool memoryMapLoadPending = false;
static bool g_wasMinimized = false;
static bool g_inForcedResize = false;
// gross hack
bool noFocusPause = false; // TOGGLE_PAUSE state to override pause on lost focus
static bool trapMouse = true; // Handles some special cases(alt+tab, win menu) when game is running and mouse is confined
static constexpr const wchar_t *szWindowClass = L"PPSSPPWnd";
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HWND GetHWND() { return hwndMain; }
HINSTANCE GetHInstance() { return hInst; }
void SetKeepScreenBright(bool keepBright) {
g_keepScreenBright = keepBright;
}
void Init(HINSTANCE hInstance) {
// Register classes - Main Window
WNDCLASSEX wcex{};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = 0; // Show in taskbar
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // or NULL?
wcex.lpszMenuName = g_Config.bShowMenuBar ? (LPCWSTR)IDR_MENU1 : NULL;
wcex.lpszClassName = szWindowClass;
wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_PPSSPP);
wcex.hIconSm = (HICON)LoadImage(hInstance, (LPCTSTR)IDI_PPSSPP, IMAGE_ICON, 16, 16, LR_SHARED);
RegisterClassEx(&wcex);
}
void SavePosition() {
if (g_Config.bFullScreen) {
// Don't save the position of the full screen window. We keep the old saved position around.
return;
}
WINDOWPLACEMENT placement{};
GetWindowPlacement(hwndMain, &placement);
WindowSizeState sizeState = ShowCmdToWindowSizeState(placement.showCmd);
if (sizeState == WindowSizeState::Minimized) {
// Don't save minimized position.
sizeState = WindowSizeState::Normal;
}
g_Config.iWindowSizeState = (int)sizeState;
g_Config.iWindowX = placement.rcNormalPosition.left;
g_Config.iWindowY = placement.rcNormalPosition.top;
g_Config.iWindowWidth = placement.rcNormalPosition.right - placement.rcNormalPosition.left;
g_Config.iWindowHeight = placement.rcNormalPosition.bottom - placement.rcNormalPosition.top;
}
static void GetWindowSizeAtResolution(int xres, int yres, int *windowWidth, int *windowHeight) {
RECT rc{};
rc.right = xres;
rc.bottom = yres;
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, TRUE);
*windowWidth = rc.right - rc.left;
*windowHeight = rc.bottom - rc.top;
}
void SetWindowSize(int zoom) {
AssertCurrentThreadName("Main");
if (g_Config.bFullScreen) {
return;
}
// Actually, auto mode should be more granular...
int width, height;
const DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());
if (config.InternalRotationIsPortrait()) {
GetWindowSizeAtResolution(272 * (int)zoom, 480 * (int)zoom, &width, &height);
} else {
GetWindowSizeAtResolution(480 * (int)zoom, 272 * (int)zoom, &width, &height);
}
g_Config.iWindowWidth = width;
g_Config.iWindowHeight = height;
MoveWindow(hwndMain, g_Config.iWindowX, g_Config.iWindowY, width, height, TRUE);
}
void SetInternalResolution(int res) {
if (res >= 0 && res <= RESOLUTION_MAX)
g_Config.iInternalResolution = res;
else {
if (++g_Config.iInternalResolution > RESOLUTION_MAX)
g_Config.iInternalResolution = 0;
}
System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED);
}
void CorrectCursor() {
const bool autoHide = ((g_Config.bFullScreen && !mouseButtonDown) || (g_Config.bMouseControl && trapMouse)) && GetUIState() == UISTATE_INGAME;
if (autoHide && (hideCursor || g_Config.bMouseControl)) {
while (cursorCounter >= 0) {
cursorCounter = ShowCursor(FALSE);
}
if (g_Config.bMouseConfine) {
RECT rc;
GetClientRect(hwndMain, &rc);
ClientToScreen(hwndMain, reinterpret_cast<POINT*>(&rc.left));
ClientToScreen(hwndMain, reinterpret_cast<POINT*>(&rc.right));
ClipCursor(&rc);
}
} else {
hideCursor = !autoHide;
if (cursorCounter < 0) {
cursorCounter = ShowCursor(TRUE);
SetCursor(LoadCursor(NULL, IDC_ARROW));
ClipCursor(NULL);
}
}
}
static void HandleSizeChange() {
Native_NotifyWindowHidden(false);
if (!g_Config.bPauseWhenMinimized) {
System_PostUIMessage(UIMessage::WINDOW_MINIMIZED, "false");
}
int width, height;
W32Util::GetWindowRes(hwndMain, &width, &height);
// Setting pixelWidth to be too small could have odd consequences.
if (width >= 4 && height >= 4) {
// The framebuffer manager reads these once per frame, hopefully safe enough.. should really use a mutex or some
// much better mechanism.
PSP_CoreParameter().pixelWidth = width;
PSP_CoreParameter().pixelHeight = height;
}
DEBUG_LOG(Log::System, "Pixel width/height: %dx%d", PSP_CoreParameter().pixelWidth, PSP_CoreParameter().pixelHeight);
if (Native_UpdateScreenScale(width, height, UIScaleFactorToMultiplier(g_Config.iUIScaleFactor))) {
System_PostUIMessage(UIMessage::GPU_DISPLAY_RESIZED);
System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED);
}
}
void ApplyFullscreenState(HWND hWnd, bool goFullscreen) {
GraphicsContext *graphicsContext = PSP_CoreParameter().graphicsContext;
// Make sure no rendering is happening during the switch.
if (graphicsContext) {
graphicsContext->Pause();
}
const DWORD prevStyle = GetWindowLong(hWnd, GWL_STYLE);
const bool isCurrentlyFullscreen = !(prevStyle & WS_OVERLAPPEDWINDOW);
if (goFullscreen && !isCurrentlyFullscreen) {
INFO_LOG(Log::System, "ApplyFullscreenState: Entering fullscreen from %s mode at %dx%d+%d+%d",
WindowSizeStateToString((WindowSizeState)g_Config.iWindowSizeState),
g_Config.iWindowWidth, g_Config.iWindowHeight,
g_Config.iWindowX, g_Config.iWindowY);
// Transitioning to Fullscreen
WINDOWPLACEMENT wp = {sizeof(WINDOWPLACEMENT)};
if (GetWindowPlacement(hWnd, &wp)) {
g_Config.iWindowX = wp.rcNormalPosition.left;
g_Config.iWindowY = wp.rcNormalPosition.top;
g_Config.iWindowWidth = wp.rcNormalPosition.right - wp.rcNormalPosition.left;
g_Config.iWindowHeight = wp.rcNormalPosition.bottom - wp.rcNormalPosition.top;
}
if (g_Config.bFullScreenMulti) {
SetMenu(hWnd, NULL);
// Strip all decorations
SetWindowLong(hWnd, GWL_STYLE, (prevStyle & ~WS_OVERLAPPEDWINDOW) | WS_POPUP);
// Maximize isn't enough to display on all monitors.
// Remember that negative coordinates may be valid.
const int totalX = GetSystemMetrics(SM_XVIRTUALSCREEN);
const int totalY = GetSystemMetrics(SM_YVIRTUALSCREEN);
const int totalWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
const int totalHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
SetWindowPos(hWnd, HWND_TOP,
totalX, totalY,
totalWidth, totalHeight,
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
ShowWindow(hWnd, SW_SHOW);
} else {
MONITORINFO mi = {sizeof(mi)};
if (GetMonitorInfo(MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST), &mi)) {
SetMenu(hWnd, NULL);
// Strip all decorations
SetWindowLong(hWnd, GWL_STYLE, (prevStyle & ~WS_OVERLAPPEDWINDOW) | WS_POPUP);
SetWindowPos(hWnd, HWND_TOP,
mi.rcMonitor.left, mi.rcMonitor.top,
mi.rcMonitor.right - mi.rcMonitor.left,
mi.rcMonitor.bottom - mi.rcMonitor.top,
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
}
ShowWindow(hWnd, SW_SHOW);
}
} else if (!goFullscreen && isCurrentlyFullscreen) {
INFO_LOG(Log::System, "ApplyFullscreenState: Exiting fullscreen to %s mode at %dx%d+%d+%d",
WindowSizeStateToString((WindowSizeState)g_Config.iWindowSizeState),
g_Config.iWindowWidth, g_Config.iWindowHeight,
g_Config.iWindowX, g_Config.iWindowY);
// Transitioning to Windowed
SetWindowLong(hWnd, GWL_STYLE, (prevStyle & ~WS_POPUP) | WS_OVERLAPPEDWINDOW);
if (g_Config.bShowMenuBar) {
SetMenu(hWnd, g_hMenu);
}
WINDOWPLACEMENT wp = {sizeof(WINDOWPLACEMENT)};
wp.showCmd = WindowSizeStateToShowCmd((WindowSizeState)g_Config.iWindowSizeState);
wp.rcNormalPosition = {g_Config.iWindowX, g_Config.iWindowY, g_Config.iWindowX + g_Config.iWindowWidth, g_Config.iWindowY + g_Config.iWindowHeight};
if (wp.showCmd == SW_SHOWMINIMIZED) wp.showCmd = SW_SHOWNORMAL;
SetWindowPlacement(hWnd, &wp);
SetWindowPos(hWnd, NULL, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
}
CorrectCursor();
ShowOwnedPopups(hwndMain, goFullscreen ? FALSE : TRUE);
W32Util::MakeTopMost(hwndMain, g_Config.bTopMost);
WindowsRawInput::NotifyMenu();
if (graphicsContext) {
graphicsContext->Resume();
}
}
void Minimize() {
ShowWindow(hwndMain, SW_MINIMIZE);
g_InputManager.LoseFocus();
}
// TODO: Currently unused.
RECT DetermineDefaultWindowRectangle() {
const int virtualScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
const int virtualScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
const int virtualScreenX = GetSystemMetrics(SM_XVIRTUALSCREEN);
const int virtualScreenY = GetSystemMetrics(SM_YVIRTUALSCREEN);
const int currentScreenWidth = GetSystemMetrics(SM_CXSCREEN);
const int currentScreenHeight = GetSystemMetrics(SM_CYSCREEN);
bool resetPositionX = true;
bool resetPositionY = true;
if (g_Config.iWindowWidth > 0 && g_Config.iWindowHeight > 0 && !g_Config.bFullScreen) {
bool visibleHorizontally = ((g_Config.iWindowX + g_Config.iWindowWidth) >= virtualScreenX) &&
((g_Config.iWindowX + g_Config.iWindowWidth) < (virtualScreenWidth + g_Config.iWindowWidth));
bool visibleVertically = ((g_Config.iWindowY + g_Config.iWindowHeight) >= virtualScreenY) &&
((g_Config.iWindowY + g_Config.iWindowHeight) < (virtualScreenHeight + g_Config.iWindowHeight));
if (visibleHorizontally)
resetPositionX = false;
if (visibleVertically)
resetPositionY = false;
}
// Try to workaround #9563.
if (!resetPositionY && g_Config.iWindowY < 0) {
g_Config.iWindowY = 0;
}
int windowWidth = g_Config.iWindowWidth;
int windowHeight = g_Config.iWindowHeight;
// First, get the w/h right.
if (windowWidth <= 0 || windowHeight <= 0) {
DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());
const bool portrait = config.InternalRotationIsPortrait();
// We want to adjust for DPI but still get an integer pixel scaling ratio.
double dpi_scale = 96.0 / System_GetPropertyFloat(SYSPROP_DISPLAY_DPI);
int scale = (int)ceil(2.0 / dpi_scale);
GetWindowSizeAtResolution(scale * (portrait ? 272 : 480), scale * (portrait ? 480 : 272), &windowWidth, &windowHeight);
}
// Then center if necessary. One dimension at a time.
// Max is to make sure that if we end up making the window bigger than the screen (which is not ideal), the top left
// corner, and thus the menu etc, will be visible. Also potential workaround for #9563.
int x = g_Config.iWindowX;
int y = g_Config.iWindowY;
if (resetPositionX) {
x = std::max(0, (currentScreenWidth - windowWidth) / 2);
}
if (resetPositionY) {
y = std::max(0, (currentScreenHeight - windowHeight) / 2);
}
RECT rc;
rc.left = x;
rc.right = rc.left + windowWidth;
rc.top = y;
rc.bottom = rc.top + windowHeight;
return rc;
}
void UpdateWindowTitle() {
std::wstring title;
{
std::lock_guard<std::mutex> lock(g_windowTitleLock);
title = g_windowTitle;
}
if (PPSSPP_ID >= 1 && GetInstancePeerCount() > 1) {
title.append(ConvertUTF8ToWString(StringFromFormat(" (instance: %d)", (int)PPSSPP_ID)));
}
SetWindowText(hwndMain, title.c_str());
}
void SetWindowTitle(const wchar_t *title) {
{
std::lock_guard<std::mutex> lock(g_windowTitleLock);
g_windowTitle = title;
}
PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_WINDOW_TITLE_CHANGED, 0, 0);
}
BOOL Show(HINSTANCE hInstance) {
hInst = hInstance; // Store instance handle in our global variable.
hwndMain = CreateWindowEx(0, szWindowClass, L"", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hwndMain)
return FALSE;
g_inForcedResize = true;
g_hMenu = GetMenu(hwndMain);
WINDOWPLACEMENT placement{sizeof(WINDOWPLACEMENT)};
if ((g_Config.iWindowX == -1 && g_Config.iWindowY == -1) || g_Config.iWindowWidth < 20 || g_Config.iWindowHeight < 20) {
RECT rc = DetermineDefaultWindowRectangle();
// Should be a first boot, or just bad parameters. Reset.
g_Config.iWindowSizeState = (int)WindowSizeState::Normal;
g_Config.iWindowX = rc.left;
g_Config.iWindowY = rc.top;
g_Config.iWindowWidth = rc.right - rc.left;
g_Config.iWindowHeight = rc.bottom - rc.top;
}
// Start as hidden in fullscreen mode. Makes for a nicer transition.
placement.showCmd = g_Config.bFullScreen ? SW_HIDE : WindowSizeStateToShowCmd((WindowSizeState)g_Config.iWindowSizeState);
placement.rcNormalPosition.left = g_Config.iWindowX;
placement.rcNormalPosition.top = g_Config.iWindowY;
placement.rcNormalPosition.right = g_Config.iWindowX + g_Config.iWindowWidth;
placement.rcNormalPosition.bottom = g_Config.iWindowY + g_Config.iWindowHeight;
SetWindowPlacement(hwndMain, &placement);
// SetWindowLong(hwndMain, GWL_EXSTYLE, WS_EX_APPWINDOW);
const DWM_WINDOW_CORNER_PREFERENCE pref = DWMWCP_DONOTROUND;
DwmSetWindowAttribute(hwndMain, DWMWA_WINDOW_CORNER_PREFERENCE, &pref, sizeof(pref));
ApplyFullscreenState(hwndMain, g_Config.bFullScreen);
if (!g_Config.bShowMenuBar) {
SetMenu(hwndMain, NULL);
} else {
MainMenuInit(hwndMain, g_hMenu);
}
// Accept dragged files.
DragAcceptFiles(hwndMain, TRUE);
hideCursor = true;
SetTimer(hwndMain, TIMER_CURSORUPDATE, CURSORUPDATE_INTERVAL_MS, 0);
W32Util::MakeTopMost(hwndMain, g_Config.bTopMost);
touchHandler.registerTouchWindow(hwndMain);
WindowsRawInput::Init();
UpdateWindow(hwndMain);
SetFocus(hwndMain);
g_inForcedResize = false;
return TRUE;
}
void CreateDisasmWindow() {
if (!disasmWindow) {
disasmWindow = new CDisasm(MainWindow::GetHInstance(), MainWindow::GetHWND(), currentDebugMIPS);
DialogManager::AddDlg(disasmWindow);
}
if (disasmMapLoadPending) {
disasmWindow->NotifyMapLoaded();
disasmMapLoadPending = false;
}
}
void CreateGeDebuggerWindow() {
#if PPSSPP_API(ANY_GL)
if (!geDebuggerWindow) {
geDebuggerWindow = new CGEDebugger(MainWindow::GetHInstance(), MainWindow::GetHWND());
DialogManager::AddDlg(geDebuggerWindow);
}
#endif
}
void CreateMemoryWindow() {
if (!memoryWindow) {
memoryWindow = new CMemoryDlg(MainWindow::GetHInstance(), MainWindow::GetHWND(), currentDebugMIPS);
DialogManager::AddDlg(memoryWindow);
}
if (memoryMapLoadPending) {
memoryWindow->NotifyMapLoaded();
memoryMapLoadPending = false;
}
}
void CreateVFPUWindow() {
if (!vfpudlg) {
vfpudlg = new CVFPUDlg(MainWindow::GetHInstance(), MainWindow::GetHWND(), currentDebugMIPS);
DialogManager::AddDlg(vfpudlg);
}
}
void NotifyDebuggerMapLoaded() {
disasmMapLoadPending = disasmWindow == nullptr;
memoryMapLoadPending = memoryWindow == nullptr;
if (!disasmMapLoadPending)
disasmWindow->NotifyMapLoaded();
if (!memoryMapLoadPending)
memoryWindow->NotifyMapLoaded();
}
void DestroyDebugWindows() {
DialogManager::RemoveDlg(disasmWindow);
delete disasmWindow;
disasmWindow = nullptr;
#if PPSSPP_API(ANY_GL)
DialogManager::RemoveDlg(geDebuggerWindow);
delete geDebuggerWindow;
geDebuggerWindow = nullptr;
#endif
DialogManager::RemoveDlg(memoryWindow);
delete memoryWindow;
memoryWindow = nullptr;
DialogManager::RemoveDlg(vfpudlg);
delete vfpudlg;
vfpudlg = nullptr;
}
bool ConfirmAction(HWND hWnd, bool actionIsReset) {
const GlobalUIState state = GetUIState();
if (state == UISTATE_MENU || state == UISTATE_EXIT) {
return true;
}
std::string confirmExitMessage = GetConfirmExitMessage();
if (confirmExitMessage.empty()) {
return true;
}
auto di = GetI18NCategory(I18NCat::DIALOG);
if (!actionIsReset) {
confirmExitMessage += '\n';
confirmExitMessage += di->T("Are you sure you want to exit?");
} else {
// Reset is bit rarer, let's just omit the extra message for now.
}
return IDYES == MessageBox(hWnd, ConvertUTF8ToWString(confirmExitMessage).c_str(), ConvertUTF8ToWString(di->T("Exit")).c_str(), MB_YESNO | MB_ICONQUESTION);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
static bool firstErase = true;
LRESULT darkResult = 0;
if (UAHDarkModeWndProc(hWnd, message, wParam, lParam, &darkResult)) {
return darkResult;
}
static bool first = true;
switch (message) {
case WM_CREATE:
first = true;
if (!IsVistaOrHigher()) {
// Remove the D3D11 choice on versions below XP
RemoveMenu(GetMenu(hWnd), ID_OPTIONS_DIRECT3D11, MF_BYCOMMAND);
}
if (g_darkModeSupported) {
SendMessageW(hWnd, WM_THEMECHANGED, 0, 0);
}
SetAssertDialogParent(hWnd);
break;
case WM_USER_RUN_CALLBACK:
{
auto callback = reinterpret_cast<void (*)(void *window, void *userdata)>(wParam);
void *userdata = reinterpret_cast<void *>(lParam);
callback(hWnd, userdata);
break;
}
case WM_USER_GET_BASE_POINTER:
Reporting::NotifyDebugger();
switch (lParam) {
case 0: return (u32)(u64)Memory::base;
case 1: return (u32)((u64)Memory::base >> 32);
case 2: return (u32)(u64)(&Memory::base);
case 3: return (u32)((u64)(&Memory::base) >> 32);
default:
return 0;
}
break;
case WM_USER_GET_EMULATION_STATE:
return (u32)(Core_IsActive() && GetUIState() == UISTATE_INGAME);
case WM_USER_GET_CURRENT_GAMEID:
{
// Return game ID as four u32 values
// wParam: 0-3 = which u32 to return (chars 0-3, 4-7, 8-11, 12-15)
// Returns: packed u32 with 4 bytes of game ID
if (!PSP_IsInited()) {
return 0;
}
const std::string gameID = Reporting::CurrentGameID();
if (gameID.empty()) {
return 0;
}
const size_t offset = (wParam & 0x3) * 4; // 0, 4, 8, 12
u32 packed = 0;
for (size_t i = 0; i < 4; ++i) {
if (offset + i < gameID.length()) {
const u8 c = static_cast<u8>(gameID[offset + i]);
packed |= ((u32)c << (i * 8));
}
}
return packed;
}
case WM_USER_GET_MODULE_INFO:
{
// Get module information by name
// wParam: pointer to module name (null-terminated string)
// lParam: 0 = address, 1 = size, 2 = active flag
// Returns: u64 packed with module info, or 0 if not found
if (!PSP_IsInited() || !g_symbolMap)
{
return 0;
}
const char* moduleName = reinterpret_cast<const char*>(wParam);
if (!moduleName)
{
return 0;
}
// Get all modules from symbol map
auto modules = g_symbolMap->getAllModules();
for (const auto& module : modules)
{
if (module.name == moduleName)
{
switch (lParam)
{
case 0:
// Return address as u32 (low 32 bits)
return (u64)module.address;
case 1:
// Return size as u32 (low 32 bits)
return (u64)module.size;
case 2:
// Return active flag in bit 0, padded with zeros
return (u64)(module.active ? 1 : 0);
case 3:
// Return all info packed: address (bits 0-31), size (bits 32-62), active (bit 63)
return ((u64)module.address) | (((u64)module.size) << 32) | (module.active ? (1ULL << 63) : 0);
default:
return 0;
}
}
}
// Module not found
return 0;
}
// Hack to kill the white line underneath the menubar.
// From https://stackoverflow.com/questions/57177310/how-to-paint-over-white-line-between-menu-bar-and-client-area-of-window
case WM_NCPAINT:
case WM_NCACTIVATE:
{
if (!IsDarkModeEnabled() || IsIconic(hWnd)) {
return DefWindowProc(hWnd, message, wParam, lParam);
}
auto result = DefWindowProc(hWnd, message, wParam, lParam);
// Paint over the line with pure black. Could also try to figure out the dark theme color.
HDC hdc = GetWindowDC(hWnd);
RECT r = W32Util::GetNonclientMenuBorderRect(hWnd);
HBRUSH red = CreateSolidBrush(RGB(0, 0, 0));
FillRect(hdc, &r, red);
DeleteObject(red);
ReleaseDC(hWnd, hdc);
return result;
}
case WM_GETMINMAXINFO:
{
MINMAXINFO *minmax = reinterpret_cast<MINMAXINFO *>(lParam);
RECT rc = { 0 };
const DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());
bool portrait = config.InternalRotationIsPortrait();
rc.right = portrait ? 272 : 480;
rc.bottom = portrait ? 480 : 272;
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, TRUE);
minmax->ptMinTrackSize.x = rc.right - rc.left;
minmax->ptMinTrackSize.y = rc.bottom - rc.top;
return 0;
}
case WM_ACTIVATE:
{
UpdateWindowTitle();
bool pause = true;
if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) {
WindowsRawInput::GainFocus();
if (!IsIconic(GetHWND())) {
g_InputManager.GainFocus();
}
g_activeWindow = WINDOW_MAINWINDOW;
pause = false;
} else {
g_activeWindow = WINDOW_OTHER;
}
if (!noFocusPause && g_Config.bPauseOnLostFocus && GetUIState() == UISTATE_INGAME) {
if (pause != Core_IsStepping()) {
if (disasmWindow) {
SendMessage(disasmWindow->GetDlgHandle(), WM_COMMAND, IDC_STOPGO, 0);
} else {
if (pause) {
Core_Break(BreakReason::UIFocus, 0);
} else if (Core_BreakReason() == BreakReason::UIFocus) {
Core_Resume();
}
}
}
}
if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) {
System_PostUIMessage(UIMessage::GOT_FOCUS);
hasFocus = true;
trapMouse = true;
}
if (wParam == WA_INACTIVE) {
System_PostUIMessage(UIMessage::LOST_FOCUS);
WindowsRawInput::LoseFocus();
g_InputManager.LoseFocus();
hasFocus = false;
trapMouse = false;
}
}
break;
case WM_SETFOCUS:
UpdateWindowTitle();
break;
case WM_ERASEBKGND:
if (firstErase) {
firstErase = false;
// Paint black on first erase while OpenGL stuff is loading
return DefWindowProc(hWnd, message, wParam, lParam);
}
// Then never erase, let the OpenGL drawing take care of everything.
return 1;
case WM_USER_APPLY_FULLSCREEN:
ApplyFullscreenState(hwndMain, g_Config.bFullScreen);
break;
case WM_DISPLAYCHANGE:
// If resolution changes while we are fullscreen, re-snap to the new monitor size
if (g_Config.bFullScreen) {
MONITORINFO mi = {sizeof(mi)};
if (GetMonitorInfo(MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST), &mi)) {
SetWindowPos(hWnd, HWND_TOP,
mi.rcMonitor.left, mi.rcMonitor.top,
mi.rcMonitor.right - mi.rcMonitor.left,
mi.rcMonitor.bottom - mi.rcMonitor.top,
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
}
}
break;
case WM_WINDOWPOSCHANGED:
{
// Handling this means that WM_SIZE and WM_MOVE won't be sent, except once during
// window creation for some reason.
const WINDOWPOS *pos = reinterpret_cast<WINDOWPOS*>(lParam);
if (!pos) {
// Uh?
return DefWindowProc(hWnd, message, wParam, lParam);
}
const bool sizeChanged = !(pos->flags & SWP_NOSIZE);
WINDOWPLACEMENT wp{sizeof(wp)};
GetWindowPlacement(hWnd, &wp);
if (!g_Config.bFullScreen) {
g_Config.iWindowSizeState = (int)ShowCmdToWindowSizeState(wp.showCmd);
}
switch (wp.showCmd) {
case SW_SHOWNORMAL:
case SW_SHOWMAXIMIZED:
if (hasFocus) {
g_InputManager.GainFocus();
}
if (g_wasMinimized) {
System_PostUIMessage(UIMessage::WINDOW_RESTORED, "true");
g_wasMinimized = false;
}
break;
case SW_SHOWMINIMIZED:
Native_NotifyWindowHidden(true);
if (!g_Config.bPauseWhenMinimized) {
System_PostUIMessage(UIMessage::WINDOW_MINIMIZED, "true");
}
g_InputManager.LoseFocus();
g_wasMinimized = true;
break;
default:
break;
}
if (sizeChanged) {
// Check that we're not in a resize that we ourselves are performing.
if (g_Config.bFullScreen && !g_inForcedResize) {
MONITORINFO mi = {sizeof(mi)};
if (GetMonitorInfo(MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST), &mi)) {
const int monWidth = mi.rcMonitor.right - mi.rcMonitor.left;
const int monHeight = mi.rcMonitor.bottom - mi.rcMonitor.top;
// If the new size is no longer the full monitor size, drop FS state (put back the menu
// and recalculate window decorations without actually changing the size of the window).
if (pos->cx != monWidth || pos->cy != monHeight) {
g_Config.bFullScreen = false;
if (GetMenu(hWnd) == NULL && g_Config.bShowMenuBar) {
SetMenu(hWnd, g_hMenu);
}
const DWORD style = GetWindowLong(hWnd, GWL_STYLE);
if (!(style & WS_CAPTION)) {
SetWindowLong(hWnd, GWL_STYLE, (style & ~WS_POPUP) | WS_OVERLAPPEDWINDOW);
SetWindowPos(hWnd, NULL, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE);
}
}
}
}
if (!inResizeMove) {
HandleSizeChange();
}
}
return 0;
}
case WM_ENTERSIZEMOVE:
inResizeMove = true;
break;
case WM_EXITSIZEMOVE:
inResizeMove = false;
HandleSizeChange();
break;
// Wheel events have to stay in WndProc for compatibility with older Windows(7). See #12156
case WM_MOUSEWHEEL:
{
int wheelDelta = (short)(wParam >> 16);
KeyInput key;
key.deviceId = DEVICE_ID_MOUSE;
if (wheelDelta < 0) {
key.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN;
wheelDelta = -wheelDelta;
} else {
key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP;
}
// There's no release event, but we simulate it in NativeKey/NativeFrame.
key.flags = (KeyInputFlags)((u32)KeyInputFlags::DOWN | (u32)KeyInputFlags::HAS_WHEEL_DELTA | (wheelDelta << 16));
NativeKey(key);
}
break;
case WM_TIMER:
// Hack: Take the opportunity to also show/hide the mouse cursor in fullscreen mode.
switch (wParam) {
case TIMER_CURSORUPDATE:
CorrectCursor();
return 0;
case TIMER_CURSORMOVEUPDATE:
hideCursor = true;
KillTimer(hWnd, TIMER_CURSORMOVEUPDATE);
return 0;
default:
break;
}
break;
case WM_COMMAND:
{
if (!MainThread_Ready())
return DefWindowProc(hWnd, message, wParam, lParam);
MainWindowMenu_Process(hWnd, wParam);
}
break;
case WM_INPUT:
return WindowsRawInput::Process(hWnd, wParam, lParam);
// TODO: Could do something useful with WM_INPUT_DEVICE_CHANGE?
// Not sure why we are actually getting WM_CHAR even though we use RawInput, but alright..
case WM_CHAR:
return WindowsRawInput::ProcessChar(hWnd, wParam, lParam);
case WM_DEVICECHANGE:
#ifndef _M_ARM
DinputDevice::CheckDevices();
#endif
if (winCamera)
winCamera->CheckDevices();
if (winMic)
winMic->CheckDevices();
return DefWindowProc(hWnd, message, wParam, lParam);
case WM_VERYSLEEPY_MSG:
switch (wParam) {
case VERYSLEEPY_WPARAM_SUPPORTED:
return TRUE;
case VERYSLEEPY_WPARAM_GETADDRINFO:
{
VerySleepy_AddrInfo *info = (VerySleepy_AddrInfo *)lParam;
const u8 *ptr = (const u8 *)info->addr;
std::string name;
std::lock_guard<std::recursive_mutex> guard(MIPSComp::jitLock);
if (MIPSComp::jit && MIPSComp::jit->DescribeCodePtr(ptr, name)) {
swprintf_s(info->name, L"Jit::%S", name.c_str());
return TRUE;
}
if (gpu && gpu->DescribeCodePtr(ptr, name)) {
swprintf_s(info->name, L"GPU::%S", name.c_str());
return TRUE;
}
}
return FALSE;
default:
return FALSE;
}
break;
case WM_DROPFILES:
{
if (!MainThread_Ready()) {
return DefWindowProc(hWnd, message, wParam, lParam);
}
const HDROP hdrop = (HDROP)wParam;
const int count = DragQueryFile(hdrop, 0xFFFFFFFF, 0, 0);
if (count != 1) {
// TODO: Translate? Or just not bother?
MessageBox(hwndMain, L"You can only load one file at a time", L"Error", MB_ICONINFORMATION);
} else {
wchar_t filename[1024];
if (DragQueryFile(hdrop, 0, filename, ARRAY_SIZE(filename)) != 0) {
const std::string utf8_filename = ReplaceAll(ConvertWStringToUTF8(filename), "\\", "/");
System_PostUIMessage(UIMessage::REQUEST_GAME_BOOT, utf8_filename);
}
}
DragFinish(hdrop);
}
break;
case WM_CLOSE:
{
if (ConfirmAction(hWnd, false)) {
DestroyWindow(hWnd);
}
return 0;
}
case WM_DESTROY:
SavePosition();
g_InputManager.StopPolling();
g_InputManager.Shutdown();
WindowsRawInput::Shutdown();
MainThread_Stop();
KillTimer(hWnd, TIMER_CURSORUPDATE);
KillTimer(hWnd, TIMER_CURSORMOVEUPDATE);
// Main window is gone, this tells the message loop to exit.
PostQuitMessage(0);
return 0;
case WM_USER + 1:
NotifyDebuggerMapLoaded();
if (disasmWindow)
disasmWindow->UpdateDialog();
break;
case WM_USER_SAVESTATE_FINISH:
SetCursor(LoadCursor(0, IDC_ARROW));
break;
case WM_USER_UPDATE_UI:
TranslateMenus(hwndMain, g_hMenu);
// Update checked status immediately for accelerators.
UpdateMenus(nullptr);
break;
case WM_USER_WINDOW_TITLE_CHANGED:
UpdateWindowTitle();
break;
case WM_USER_RESTART_EMUTHREAD:
NativeSetRestarting();
g_InputManager.StopPolling();
MainThread_Stop();
UpdateUIState(UISTATE_MENU);
MainThread_Start(g_Config.iGPUBackend == (int)GPUBackend::OPENGL);
g_InputManager.BeginPolling();
break;
case WM_USER_SWITCHUMD_UPDATED:
UpdateSwitchUMD();
break;
case WM_USER_DESTROY:
DestroyWindow(hWnd);
break;
case WM_INITMENUPOPUP:
// Called when a menu or submenu is about to be opened.
UpdateMenus((HMENU)wParam);
WindowsRawInput::NotifyMenu();
trapMouse = false;
break;
case WM_EXITMENULOOP:
// Called when menu is closed.
trapMouse = true;
break;
// Turn off the screensaver if in-game.
// Note that if there's a screensaver password, this simple method
// doesn't work on Vista or higher.
case WM_SYSCOMMAND:
// Disable Alt key for menu if it's been mapped.
if (wParam == SC_KEYMENU && (lParam >> 16) <= 0) {
if (KeyMap::IsKeyMapped(DEVICE_ID_KEYBOARD, NKCODE_ALT_LEFT) || KeyMap::IsKeyMapped(DEVICE_ID_KEYBOARD, NKCODE_ALT_RIGHT)) {
return 0;
}
}
if (g_keepScreenBright) {
switch (wParam) {
case SC_SCREENSAVE:
return 0;
case SC_MONITORPOWER:
if (lParam == 1 || lParam == 2) {
return 0;
} else {
break;
}
default:
// fall down to DefWindowProc
break;
}
}
return DefWindowProc(hWnd, message, wParam, lParam);
case WM_SETTINGCHANGE:
if (g_darkModeSupported && IsColorSchemeChangeMessage(lParam)) {
SendMessageW(hWnd, WM_THEMECHANGED, 0, 0);
}
return DefWindowProc(hWnd, message, wParam, lParam);
case WM_THEMECHANGED:
{
if (g_darkModeSupported) {
_AllowDarkModeForWindow(hWnd, g_darkModeEnabled);
RefreshTitleBarThemeColor(hWnd);
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
case WM_SETCURSOR:
if ((lParam & 0xFFFF) == HTCLIENT && g_Config.bShowImDebugger) {
LPTSTR win32_cursor = 0;
if (g_Config.bShowImDebugger) {
switch (ImGui_ImplPlatform_GetCursor()) {
case ImGuiMouseCursor_Arrow: win32_cursor = IDC_ARROW; break;
case ImGuiMouseCursor_TextInput: win32_cursor = IDC_IBEAM; break;
case ImGuiMouseCursor_ResizeAll: win32_cursor = IDC_SIZEALL; break;
case ImGuiMouseCursor_ResizeEW: win32_cursor = IDC_SIZEWE; break;
case ImGuiMouseCursor_ResizeNS: win32_cursor = IDC_SIZENS; break;
case ImGuiMouseCursor_ResizeNESW: win32_cursor = IDC_SIZENESW; break;
case ImGuiMouseCursor_ResizeNWSE: win32_cursor = IDC_SIZENWSE; break;
case ImGuiMouseCursor_Hand: win32_cursor = IDC_HAND; break;
case ImGuiMouseCursor_NotAllowed: win32_cursor = IDC_NO; break;
default: break;
}
}
SetCursor(win32_cursor ? ::LoadCursor(nullptr, win32_cursor) : nullptr);
return TRUE;
} else {
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
// Mouse input. We send asynchronous touch events for minimal latency.
case WM_LBUTTONDOWN:
if (!touchHandler.hasTouch() ||
(GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN)
{
// Hack: Take the opportunity to show the cursor.
mouseButtonDown = true;
const float x = GET_X_LPARAM(lParam) * g_display.dpi_scale_x;
const float y = GET_Y_LPARAM(lParam) * g_display.dpi_scale_y;
WindowsRawInput::SetMousePos(x, y);
TouchInput touch{};
touch.flags = TouchInputFlags::DOWN | TouchInputFlags::MOUSE;
touch.buttons = 1;
touch.x = x;
touch.y = y;
NativeTouch(touch);
SetCapture(hWnd);
// Simulate doubleclick, doesn't work with RawInput enabled
static double lastMouseDownTime;
static float lastMouseDownX = -1.0f;
static float lastMouseDownY = -1.0f;
const double now = time_now_d();
if ((now - lastMouseDownTime) < 0.001 * GetDoubleClickTime()) {
const float dx = lastMouseDownX - x;
const float dy = lastMouseDownY - y;
const float distSq = dx * dx + dy * dy;
if (distSq < 3.0f*3.0f && !g_Config.bShowTouchControls && !g_Config.bShowImDebugger && !g_Config.bMouseControl && GetUIState() == UISTATE_INGAME && g_Config.bFullscreenOnDoubleclick) {
g_Config.bFullScreen = !g_Config.bFullScreen;
SendApplyFullscreenState();
}
lastMouseDownTime = 0.0;
} else {
lastMouseDownTime = now;
}
lastMouseDownX = x;
lastMouseDownY = y;
}
break;
case WM_MOUSEMOVE:
if (!touchHandler.hasTouch() ||
(GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN)
{
// Hack: Take the opportunity to show the cursor.
mouseButtonDown = (wParam & MK_LBUTTON) != 0;
int cursorX = GET_X_LPARAM(lParam);
int cursorY = GET_Y_LPARAM(lParam);
// Require at least 2 pixels of movement to reset the hide timer.
if (abs(cursorX - prevCursorX) > 1 || abs(cursorY - prevCursorY) > 1) {
hideCursor = false;
SetTimer(hwndMain, TIMER_CURSORMOVEUPDATE, CURSORUPDATE_MOVE_TIMESPAN_MS, 0);
}
prevCursorX = cursorX;
prevCursorY = cursorY;
const float x = (float)cursorX * g_display.dpi_scale_x;
const float y = (float)cursorY * g_display.dpi_scale_y;
WindowsRawInput::SetMousePos(x, y);
// Mouse moves now happen also when no button is pressed.
TouchInput touch{};
touch.flags = TouchInputFlags::MOVE | TouchInputFlags::MOUSE;
if (wParam & MK_LBUTTON) {
touch.buttons |= 1;
}
if (wParam & MK_RBUTTON) {
touch.buttons |= 2;
}
touch.x = x;
touch.y = y;
NativeTouch(touch);
}
break;
case WM_LBUTTONUP:
if (!touchHandler.hasTouch() ||
(GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN)
{
// Hack: Take the opportunity to hide the cursor.
mouseButtonDown = false;
const float x = (float)GET_X_LPARAM(lParam) * g_display.dpi_scale_x;
const float y = (float)GET_Y_LPARAM(lParam) * g_display.dpi_scale_y;
WindowsRawInput::SetMousePos(x, y);
TouchInput touch{};
touch.buttons = 1;
touch.flags = TouchInputFlags::UP | TouchInputFlags::MOUSE;
touch.x = x;
touch.y = y;
NativeTouch(touch);
ReleaseCapture();
}
break;
case WM_TOUCH:
touchHandler.handleTouchEvent(hWnd, message, wParam, lParam);
return 0;
case WM_RBUTTONDOWN:
{
TouchInput touch{};
touch.buttons = 2;
touch.flags = TouchInputFlags::DOWN | TouchInputFlags::MOUSE;
touch.x = GET_X_LPARAM(lParam) * g_display.dpi_scale_x;
touch.y = GET_Y_LPARAM(lParam) * g_display.dpi_scale_y;
NativeTouch(touch);
break;
}
case WM_RBUTTONUP:
{
TouchInput touch{};
touch.buttons = 2;
touch.flags = TouchInputFlags::UP | TouchInputFlags::MOUSE;
touch.x = GET_X_LPARAM(lParam) * g_display.dpi_scale_x;
touch.y = GET_Y_LPARAM(lParam) * g_display.dpi_scale_y;
NativeTouch(touch);
break;
}
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
void ToggleDebugConsoleVisibility() {
if (!g_Config.bEnableLogging) {
g_logManager.GetConsoleListener()->Show(false);
EnableMenuItem(g_hMenu, ID_DEBUG_LOG, MF_GRAYED);
}
else {
g_logManager.GetConsoleListener()->Show(true);
EnableMenuItem(g_hMenu, ID_DEBUG_LOG, MF_ENABLED);
}
}
void SendApplyFullscreenState() {
PostMessage(hwndMain, WM_USER_APPLY_FULLSCREEN, 0, 0);
}
void RunCallbackInWndProc(void (*callback)(void *, void *), void *userdata) {
PostMessage(hwndMain, WM_USER_RUN_CALLBACK, reinterpret_cast<WPARAM>(callback), reinterpret_cast<LPARAM>(userdata));
}
} // namespace MainWindow