CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Windows/MainWindow.cpp
Views: 1401
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617// TODO: Get rid of the internal window.18// Tried before but Intel drivers screw up when minimizing, or something ?1920#include "stdafx.h"2122#include "ppsspp_config.h"2324#include "Common/CommonWindows.h"25#include "Common/OSVersion.h"2627#include <Windowsx.h>28#include <shellapi.h>29#include <commctrl.h>30#include <string>31#include <dwmapi.h>3233#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"4344#include "Core/Core.h"45#include "Core/Config.h"46#include "Core/ConfigValues.h"47#include "Core/Debugger/SymbolMap.h"48#include "Core/Instance.h"49#include "Core/KeyMap.h"50#include "Core/MIPS/JitCommon/JitCommon.h"51#include "Core/MIPS/JitCommon/JitBlockCache.h"52#include "Core/Reporting.h"53#include "Windows/InputBox.h"54#include "Windows/InputDevice.h"55#if PPSSPP_API(ANY_GL)56#include "Windows/GPU/WindowsGLContext.h"57#include "Windows/GEDebugger/GEDebugger.h"58#endif59#include "Windows/W32Util/DarkMode.h"60#include "Windows/W32Util/UAHMenuBar.h"61#include "Windows/Debugger/Debugger_Disasm.h"62#include "Windows/Debugger/Debugger_MemoryDlg.h"6364#include "Common/GraphicsContext.h"6566#include "Windows/main.h"67#ifndef _M_ARM68#include "Windows/DinputDevice.h"69#endif70#include "Windows/EmuThread.h"71#include "Windows/resource.h"7273#include "Windows/MainWindow.h"74#include "Common/Log/LogManager.h"75#include "Common/Log/ConsoleListener.h"76#include "Windows/W32Util/DialogManager.h"77#include "Windows/W32Util/ShellUtil.h"78#include "Windows/W32Util/Misc.h"79#include "Windows/RawInput.h"80#include "Windows/CaptureDevice.h"81#include "Windows/TouchInputHandler.h"82#include "Windows/MainWindowMenu.h"83#include "GPU/GPUInterface.h"84#include "UI/OnScreenDisplay.h"85#include "UI/GameSettingsScreen.h"8687#define MOUSEEVENTF_FROMTOUCH_NOPEN 0xFF515780 //http://msdn.microsoft.com/en-us/library/windows/desktop/ms703320(v=vs.85).aspx88#define MOUSEEVENTF_MASK_PLUS_PENTOUCH 0xFFFFFF808990// See https://github.com/unknownbrackets/verysleepy/commit/fc1b1b3bd6081fae3566cdb542d896e413238b7191int verysleepy__useSendMessage = 1;9293const UINT WM_VERYSLEEPY_MSG = WM_APP + 0x3117;94const UINT WM_USER_GET_BASE_POINTER = WM_APP + 0x3118; // 0xB11895const UINT WM_USER_GET_EMULATION_STATE = WM_APP + 0x3119; // 0xB1199697// Respond TRUE to a message with this param value to indicate support.98const WPARAM VERYSLEEPY_WPARAM_SUPPORTED = 0;99// Respond TRUE to a message wit this param value after filling in the addr name.100const WPARAM VERYSLEEPY_WPARAM_GETADDRINFO = 1;101102struct VerySleepy_AddrInfo {103// Always zero for now.104int flags;105// This is the pointer (always passed as 64 bits.)106unsigned long long addr;107// Write the name here.108wchar_t name[256];109};110111static std::wstring windowTitle;112113#define TIMER_CURSORUPDATE 1114#define TIMER_CURSORMOVEUPDATE 2115#define CURSORUPDATE_INTERVAL_MS 1000116#define CURSORUPDATE_MOVE_TIMESPAN_MS 500117118namespace MainWindow119{120HWND hwndMain;121HWND hwndDisplay;122HWND hwndGameList;123TouchInputHandler touchHandler;124static HMENU menu;125126HINSTANCE hInst;127static int cursorCounter = 0;128static int prevCursorX = -1;129static int prevCursorY = -1;130131static bool mouseButtonDown = false;132static bool hideCursor = false;133static int g_WindowState;134static bool g_IgnoreWM_SIZE = false;135static bool inFullscreenResize = false;136static bool inResizeMove = false;137static bool hasFocus = true;138static bool g_isFullscreen = false;139static bool g_keepScreenBright = false;140141static bool disasmMapLoadPending = false;142static bool memoryMapLoadPending = false;143144// gross hack145bool noFocusPause = false; // TOGGLE_PAUSE state to override pause on lost focus146bool trapMouse = true; // Handles some special cases(alt+tab, win menu) when game is running and mouse is confined147148#define MAX_LOADSTRING 100149const TCHAR *szWindowClass = TEXT("PPSSPPWnd");150const TCHAR *szDisplayClass = TEXT("PPSSPPDisplay");151152// Forward declarations of functions included in this code module:153LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);154LRESULT CALLBACK DisplayProc(HWND, UINT, WPARAM, LPARAM);155156HWND GetHWND() {157return hwndMain;158}159160HWND GetDisplayHWND() {161return hwndDisplay;162}163164void SetKeepScreenBright(bool keepBright) {165g_keepScreenBright = keepBright;166}167168void Init(HINSTANCE hInstance) {169// Register classes - Main Window170WNDCLASSEX wcex;171memset(&wcex, 0, sizeof(wcex));172wcex.cbSize = sizeof(WNDCLASSEX);173wcex.style = 0; // Show in taskbar174wcex.lpfnWndProc = (WNDPROC)WndProc;175wcex.hInstance = hInstance;176wcex.hCursor = LoadCursor(NULL, IDC_ARROW);177wcex.hbrBackground = NULL; // Always covered by display window178wcex.lpszMenuName = (LPCWSTR)IDR_MENU1;179wcex.lpszClassName = szWindowClass;180wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_PPSSPP);181wcex.hIconSm = (HICON)LoadImage(hInstance, (LPCTSTR)IDI_PPSSPP, IMAGE_ICON, 16, 16, LR_SHARED);182RegisterClassEx(&wcex);183184WNDCLASSEX wcdisp;185memset(&wcdisp, 0, sizeof(wcdisp));186// Display Window (contained in main window)187wcdisp.cbSize = sizeof(WNDCLASSEX);188wcdisp.style = CS_HREDRAW | CS_VREDRAW;189wcdisp.lpfnWndProc = (WNDPROC)DisplayProc;190wcdisp.hInstance = hInstance;191wcdisp.hCursor = LoadCursor(NULL, IDC_ARROW);192wcdisp.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);193wcdisp.lpszMenuName = 0;194wcdisp.lpszClassName = szDisplayClass;195wcdisp.hIcon = 0;196wcdisp.hIconSm = 0;197RegisterClassEx(&wcdisp);198}199200void SavePosition() {201if (g_Config.UseFullScreen() || inFullscreenResize)202return;203204WINDOWPLACEMENT placement{};205GetWindowPlacement(hwndMain, &placement);206if (placement.showCmd == SW_SHOWNORMAL) {207RECT rc;208GetWindowRect(hwndMain, &rc);209g_Config.iWindowX = rc.left;210g_Config.iWindowY = rc.top;211g_Config.iWindowWidth = rc.right - rc.left;212g_Config.iWindowHeight = rc.bottom - rc.top;213}214}215216static void GetWindowSizeAtResolution(int xres, int yres, int *windowWidth, int *windowHeight) {217RECT rc{};218rc.right = xres;219rc.bottom = yres;220AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, TRUE);221*windowWidth = rc.right - rc.left;222*windowHeight = rc.bottom - rc.top;223}224225void SetWindowSize(int zoom) {226AssertCurrentThreadName("Main");227// Actually, auto mode should be more granular...228int width, height;229if (g_Config.IsPortrait()) {230GetWindowSizeAtResolution(272 * (int)zoom, 480 * (int)zoom, &width, &height);231} else {232GetWindowSizeAtResolution(480 * (int)zoom, 272 * (int)zoom, &width, &height);233}234g_Config.iWindowWidth = width;235g_Config.iWindowHeight = height;236MoveWindow(hwndMain, g_Config.iWindowX, g_Config.iWindowY, width, height, TRUE);237}238239void SetInternalResolution(int res) {240if (res >= 0 && res <= RESOLUTION_MAX)241g_Config.iInternalResolution = res;242else {243if (++g_Config.iInternalResolution > RESOLUTION_MAX)244g_Config.iInternalResolution = 0;245}246247System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED);248}249250void CorrectCursor() {251bool autoHide = ((g_Config.UseFullScreen() && !mouseButtonDown) || (g_Config.bMouseControl && trapMouse)) && GetUIState() == UISTATE_INGAME;252if (autoHide && (hideCursor || g_Config.bMouseControl)) {253while (cursorCounter >= 0) {254cursorCounter = ShowCursor(FALSE);255}256if (g_Config.bMouseConfine) {257RECT rc;258GetClientRect(hwndDisplay, &rc);259ClientToScreen(hwndDisplay, reinterpret_cast<POINT*>(&rc.left));260ClientToScreen(hwndDisplay, reinterpret_cast<POINT*>(&rc.right));261ClipCursor(&rc);262}263} else {264hideCursor = !autoHide;265if (cursorCounter < 0) {266cursorCounter = ShowCursor(TRUE);267SetCursor(LoadCursor(NULL, IDC_ARROW));268ClipCursor(NULL);269}270}271}272273static void HandleSizeChange(int newSizingType) {274SavePosition();275Core_NotifyWindowHidden(false);276if (!g_Config.bPauseWhenMinimized) {277System_PostUIMessage(UIMessage::WINDOW_MINIMIZED, "false");278}279280int width, height;281W32Util::GetWindowRes(hwndMain, &width, &height);282283// Moves the internal display window to match the inner size of the main window.284MoveWindow(hwndDisplay, 0, 0, width, height, TRUE);285286// Setting pixelWidth to be too small could have odd consequences.287if (width >= 4 && height >= 4) {288// The framebuffer manager reads these once per frame, hopefully safe enough.. should really use a mutex or some289// much better mechanism.290PSP_CoreParameter().pixelWidth = width;291PSP_CoreParameter().pixelHeight = height;292}293294DEBUG_LOG(Log::System, "Pixel width/height: %dx%d", PSP_CoreParameter().pixelWidth, PSP_CoreParameter().pixelHeight);295296if (UpdateScreenScale(width, height)) {297System_PostUIMessage(UIMessage::GPU_DISPLAY_RESIZED);298System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED);299}300301// Don't save the window state if fullscreen.302if (!g_Config.UseFullScreen()) {303g_WindowState = newSizingType;304}305}306307void ToggleFullscreen(HWND hWnd, bool goingFullscreen) {308GraphicsContext *graphicsContext = PSP_CoreParameter().graphicsContext;309// Make sure no rendering is happening during the switch.310if (graphicsContext) {311graphicsContext->Pause();312}313314WINDOWPLACEMENT placement = { sizeof(WINDOWPLACEMENT) };315GetWindowPlacement(hwndMain, &placement);316317int oldWindowState = g_WindowState;318inFullscreenResize = true;319g_IgnoreWM_SIZE = true;320321DWORD dwStyle;322323if (!goingFullscreen) {324dwStyle = ::GetWindowLong(hWnd, GWL_STYLE);325326// Remove popup327dwStyle &= ~WS_POPUP;328// Re-add caption and border styles.329dwStyle |= WS_OVERLAPPEDWINDOW;330} else {331// If the window was maximized before going fullscreen, make sure to restore first332// in order not to have the taskbar show up on top of PPSSPP.333if (oldWindowState == SIZE_MAXIMIZED || placement.showCmd == SW_SHOWMAXIMIZED) {334ShowWindow(hwndMain, SW_RESTORE);335}336337// Remove caption and border styles.338dwStyle = ::GetWindowLong(hWnd, GWL_STYLE);339dwStyle &= ~WS_OVERLAPPEDWINDOW;340// Add Popup341dwStyle |= WS_POPUP;342}343344::SetWindowLong(hWnd, GWL_STYLE, dwStyle);345346// Remove the menu bar. This can trigger WM_SIZE because the contents change size.347::SetMenu(hWnd, goingFullscreen || !g_Config.bShowMenuBar ? NULL : menu);348349if (g_Config.UseFullScreen() != goingFullscreen) {350g_Config.bFullScreen = goingFullscreen;351g_Config.iForceFullScreen = -1;352}353g_isFullscreen = goingFullscreen;354355g_IgnoreWM_SIZE = false;356357// Resize to the appropriate view.358// If we're returning to window mode, re-apply the appropriate size setting.359if (goingFullscreen) {360if (g_Config.bFullScreenMulti) {361// Maximize isn't enough to display on all monitors.362// Remember that negative coordinates may be valid.363int totalX = GetSystemMetrics(SM_XVIRTUALSCREEN);364int totalY = GetSystemMetrics(SM_YVIRTUALSCREEN);365int totalWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);366int totalHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);367MoveWindow(hwndMain, totalX, totalY, totalWidth, totalHeight, TRUE);368HandleSizeChange(oldWindowState);369ShowWindow(hwndMain, SW_SHOW);370} else {371ShowWindow(hwndMain, SW_MAXIMIZE);372}373} else {374ShowWindow(hwndMain, oldWindowState == SIZE_MAXIMIZED ? SW_MAXIMIZE : SW_RESTORE);375if (g_Config.bFullScreenMulti && oldWindowState != SIZE_MAXIMIZED) {376// Return the screen to where it was.377MoveWindow(hwndMain, g_Config.iWindowX, g_Config.iWindowY, g_Config.iWindowWidth, g_Config.iWindowHeight, TRUE);378}379if (oldWindowState == SIZE_MAXIMIZED) {380// WM_SIZE wasn't sent, since the size didn't change (it was full screen before and after.)381HandleSizeChange(oldWindowState);382}383}384385inFullscreenResize = false;386CorrectCursor();387388ShowOwnedPopups(hwndMain, goingFullscreen ? FALSE : TRUE);389W32Util::MakeTopMost(hwndMain, g_Config.bTopMost);390391WindowsRawInput::NotifyMenu();392393if (graphicsContext) {394graphicsContext->Resume();395}396}397398void Minimize() {399ShowWindow(hwndMain, SW_MINIMIZE);400InputDevice::LoseFocus();401}402403RECT DetermineWindowRectangle() {404const int virtualScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);405const int virtualScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);406const int virtualScreenX = GetSystemMetrics(SM_XVIRTUALSCREEN);407const int virtualScreenY = GetSystemMetrics(SM_YVIRTUALSCREEN);408const int currentScreenWidth = GetSystemMetrics(SM_CXSCREEN);409const int currentScreenHeight = GetSystemMetrics(SM_CYSCREEN);410411bool resetPositionX = true;412bool resetPositionY = true;413414if (g_Config.iWindowWidth > 0 && g_Config.iWindowHeight > 0 && !g_Config.UseFullScreen()) {415bool visibleHorizontally = ((g_Config.iWindowX + g_Config.iWindowWidth) >= virtualScreenX) &&416((g_Config.iWindowX + g_Config.iWindowWidth) < (virtualScreenWidth + g_Config.iWindowWidth));417418bool visibleVertically = ((g_Config.iWindowY + g_Config.iWindowHeight) >= virtualScreenY) &&419((g_Config.iWindowY + g_Config.iWindowHeight) < (virtualScreenHeight + g_Config.iWindowHeight));420421if (visibleHorizontally)422resetPositionX = false;423if (visibleVertically)424resetPositionY = false;425}426427// Try to workaround #9563.428if (!resetPositionY && g_Config.iWindowY < 0) {429g_Config.iWindowY = 0;430}431432int windowWidth = g_Config.iWindowWidth;433int windowHeight = g_Config.iWindowHeight;434435// First, get the w/h right.436if (windowWidth <= 0 || windowHeight <= 0) {437bool portrait = g_Config.IsPortrait();438439// We want to adjust for DPI but still get an integer pixel scaling ratio.440double dpi_scale = 96.0 / System_GetPropertyFloat(SYSPROP_DISPLAY_DPI);441int scale = (int)ceil(2.0 / dpi_scale);442443GetWindowSizeAtResolution(scale * (portrait ? 272 : 480), scale * (portrait ? 480 : 272), &windowWidth, &windowHeight);444}445446// Then center if necessary. One dimension at a time.447// Max is to make sure that if we end up making the window bigger than the screen (which is not ideal), the top left448// corner, and thus the menu etc, will be visible. Also potential workaround for #9563.449int x = g_Config.iWindowX;450int y = g_Config.iWindowY;451if (resetPositionX) {452x = std::max(0, (currentScreenWidth - windowWidth) / 2);453}454if (resetPositionY) {455y = std::max(0, (currentScreenHeight - windowHeight) / 2);456}457458RECT rc;459rc.left = x;460rc.right = rc.left + windowWidth;461rc.top = y;462rc.bottom = rc.top + windowHeight;463return rc;464}465466void UpdateWindowTitle() {467std::wstring title = windowTitle;468if (PPSSPP_ID >= 1 && GetInstancePeerCount() > 1) {469title.append(ConvertUTF8ToWString(StringFromFormat(" (instance: %d)", (int)PPSSPP_ID)));470}471SetWindowText(hwndMain, title.c_str());472}473474void SetWindowTitle(const wchar_t *title) {475windowTitle = title;476}477478BOOL Show(HINSTANCE hInstance) {479hInst = hInstance; // Store instance handle in our global variable.480RECT rc = DetermineWindowRectangle();481482u32 style = WS_OVERLAPPEDWINDOW;483484hwndMain = CreateWindowEx(0,szWindowClass, L"", style,485rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInstance, NULL);486if (!hwndMain)487return FALSE;488489SetWindowLong(hwndMain, GWL_EXSTYLE, WS_EX_APPWINDOW);490491492const DWM_WINDOW_CORNER_PREFERENCE pref = DWMWCP_DONOTROUND;493DwmSetWindowAttribute(hwndMain, DWMWA_WINDOW_CORNER_PREFERENCE, &pref, sizeof(pref));494495RECT rcClient;496GetClientRect(hwndMain, &rcClient);497498hwndDisplay = CreateWindowEx(0, szDisplayClass, L"", WS_CHILD | WS_VISIBLE,4990, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, hwndMain, 0, hInstance, 0);500if (!hwndDisplay)501return FALSE;502503menu = GetMenu(hwndMain);504505MENUINFO info;506ZeroMemory(&info,sizeof(MENUINFO));507info.cbSize = sizeof(MENUINFO);508info.cyMax = 0;509info.dwStyle = MNS_CHECKORBMP;510info.fMask = MIM_STYLE;511for (int i = 0; i < GetMenuItemCount(menu); i++) {512SetMenuInfo(GetSubMenu(menu,i), &info);513}514515// Always translate first: translating resets the menu.516TranslateMenus(hwndMain, menu);517UpdateMenus();518519// Accept dragged files.520DragAcceptFiles(hwndMain, TRUE);521522hideCursor = true;523SetTimer(hwndMain, TIMER_CURSORUPDATE, CURSORUPDATE_INTERVAL_MS, 0);524525ToggleFullscreen(hwndMain, g_Config.UseFullScreen());526527W32Util::MakeTopMost(hwndMain, g_Config.bTopMost);528529touchHandler.registerTouchWindow(hwndDisplay);530531WindowsRawInput::Init();532533SetFocus(hwndMain);534535return TRUE;536}537538void CreateDisasmWindow() {539if (!disasmWindow) {540disasmWindow = new CDisasm(MainWindow::GetHInstance(), MainWindow::GetHWND(), currentDebugMIPS);541DialogManager::AddDlg(disasmWindow);542}543if (disasmMapLoadPending)544disasmWindow->NotifyMapLoaded();545disasmMapLoadPending = false;546}547548void CreateGeDebuggerWindow() {549#if PPSSPP_API(ANY_GL)550if (!geDebuggerWindow) {551geDebuggerWindow = new CGEDebugger(MainWindow::GetHInstance(), MainWindow::GetHWND());552DialogManager::AddDlg(geDebuggerWindow);553}554#endif555}556557void CreateMemoryWindow() {558if (!memoryWindow) {559memoryWindow = new CMemoryDlg(MainWindow::GetHInstance(), MainWindow::GetHWND(), currentDebugMIPS);560DialogManager::AddDlg(memoryWindow);561}562if (memoryMapLoadPending)563memoryWindow->NotifyMapLoaded();564memoryMapLoadPending = false;565}566567void CreateVFPUWindow() {568if (!vfpudlg) {569vfpudlg = new CVFPUDlg(MainWindow::GetHInstance(), MainWindow::GetHWND(), currentDebugMIPS);570DialogManager::AddDlg(vfpudlg);571}572}573574void NotifyDebuggerMapLoaded() {575disasmMapLoadPending = disasmWindow == nullptr;576memoryMapLoadPending = memoryWindow == nullptr;577if (!disasmMapLoadPending)578disasmWindow->NotifyMapLoaded();579if (!memoryMapLoadPending)580memoryWindow->NotifyMapLoaded();581}582583void DestroyDebugWindows() {584DialogManager::RemoveDlg(disasmWindow);585delete disasmWindow;586disasmWindow = nullptr;587588#if PPSSPP_API(ANY_GL)589DialogManager::RemoveDlg(geDebuggerWindow);590delete geDebuggerWindow;591geDebuggerWindow = nullptr;592#endif593594DialogManager::RemoveDlg(memoryWindow);595delete memoryWindow;596memoryWindow = nullptr;597598DialogManager::RemoveDlg(vfpudlg);599delete vfpudlg;600vfpudlg = nullptr;601}602603LRESULT CALLBACK DisplayProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {604static bool firstErase = true;605606switch (message) {607case WM_SIZE:608break;609610case WM_SETFOCUS:611break;612613case WM_ERASEBKGND:614if (firstErase) {615firstErase = false;616// Paint black on first erase while OpenGL stuff is loading617return DefWindowProc(hWnd, message, wParam, lParam);618}619// Then never erase, let the OpenGL drawing take care of everything.620return 1;621622// Mouse input. We send asynchronous touch events for minimal latency.623case WM_LBUTTONDOWN:624if (!touchHandler.hasTouch() ||625(GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN)626{627// Hack: Take the opportunity to show the cursor.628mouseButtonDown = true;629630float x = GET_X_LPARAM(lParam) * g_display.dpi_scale_x;631float y = GET_Y_LPARAM(lParam) * g_display.dpi_scale_y;632WindowsRawInput::SetMousePos(x, y);633634TouchInput touch;635touch.id = 0;636touch.flags = TOUCH_DOWN;637touch.x = x;638touch.y = y;639NativeTouch(touch);640SetCapture(hWnd);641642// Simulate doubleclick, doesn't work with RawInput enabled643static double lastMouseDown;644static float lastMouseDownX = -1.0f;645static float lastMouseDownY = -1.0f;646double now = time_now_d();647if ((now - lastMouseDown) < 0.001 * GetDoubleClickTime()) {648float dx = lastMouseDownX - x;649float dy = lastMouseDownY - y;650float distSq = dx * dx + dy * dy;651if (distSq < 3.0f*3.0f && !g_Config.bShowTouchControls && !g_Config.bMouseControl && GetUIState() == UISTATE_INGAME && g_Config.bFullscreenOnDoubleclick) {652SendToggleFullscreen(!g_Config.UseFullScreen());653}654lastMouseDown = 0.0;655} else {656lastMouseDown = now;657}658lastMouseDownX = x;659lastMouseDownY = y;660}661break;662663case WM_MOUSEMOVE:664if (!touchHandler.hasTouch() ||665(GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN)666{667// Hack: Take the opportunity to show the cursor.668mouseButtonDown = (wParam & MK_LBUTTON) != 0;669int cursorX = GET_X_LPARAM(lParam);670int cursorY = GET_Y_LPARAM(lParam);671if (abs(cursorX - prevCursorX) > 1 || abs(cursorY - prevCursorY) > 1) {672hideCursor = false;673SetTimer(hwndMain, TIMER_CURSORMOVEUPDATE, CURSORUPDATE_MOVE_TIMESPAN_MS, 0);674}675prevCursorX = cursorX;676prevCursorY = cursorY;677678float x = (float)cursorX * g_display.dpi_scale_x;679float y = (float)cursorY * g_display.dpi_scale_y;680WindowsRawInput::SetMousePos(x, y);681682if (wParam & MK_LBUTTON) {683TouchInput touch;684touch.id = 0;685touch.flags = TOUCH_MOVE;686touch.x = x;687touch.y = y;688NativeTouch(touch);689}690}691break;692693case WM_LBUTTONUP:694if (!touchHandler.hasTouch() ||695(GetMessageExtraInfo() & MOUSEEVENTF_MASK_PLUS_PENTOUCH) != MOUSEEVENTF_FROMTOUCH_NOPEN)696{697// Hack: Take the opportunity to hide the cursor.698mouseButtonDown = false;699700float x = (float)GET_X_LPARAM(lParam) * g_display.dpi_scale_x;701float y = (float)GET_Y_LPARAM(lParam) * g_display.dpi_scale_y;702WindowsRawInput::SetMousePos(x, y);703704TouchInput touch;705touch.id = 0;706touch.flags = TOUCH_UP;707touch.x = x;708touch.y = y;709NativeTouch(touch);710ReleaseCapture();711}712break;713714case WM_TOUCH:715touchHandler.handleTouchEvent(hWnd, message, wParam, lParam);716return 0;717718default:719return DefWindowProc(hWnd, message, wParam, lParam);720}721return 0;722}723724RECT MapRectFromClientToWndCoords(HWND hwnd, const RECT & r)725{726RECT wnd_coords = r;727728// map to screen729MapWindowPoints(hwnd, NULL, reinterpret_cast<POINT *>(&wnd_coords), 2);730731RECT scr_coords;732GetWindowRect(hwnd, &scr_coords);733734// map to window coords by substracting the window coord origin in735// screen coords.736OffsetRect(&wnd_coords, -scr_coords.left, -scr_coords.top);737738return wnd_coords;739}740741RECT GetNonclientMenuBorderRect(HWND hwnd)742{743RECT r;744GetClientRect(hwnd, &r);745r = MapRectFromClientToWndCoords(hwnd, r);746int y = r.top - 1;747return {748r.left,749y,750r.right,751y + 1752};753}754755LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {756LRESULT darkResult = 0;757if (UAHDarkModeWndProc(hWnd, message, wParam, lParam, &darkResult)) {758return darkResult;759}760761switch (message) {762case WM_CREATE:763if (!IsVistaOrHigher()) {764// Remove the D3D11 choice on versions below XP765RemoveMenu(GetMenu(hWnd), ID_OPTIONS_DIRECT3D11, MF_BYCOMMAND);766}767if (g_darkModeSupported) {768SendMessageW(hWnd, WM_THEMECHANGED, 0, 0);769}770break;771772case WM_USER_RUN_CALLBACK:773{774auto callback = reinterpret_cast<void (*)(void *window, void *userdata)>(wParam);775void *userdata = reinterpret_cast<void *>(lParam);776callback(hWnd, userdata);777break;778}779case WM_USER_GET_BASE_POINTER:780Reporting::NotifyDebugger();781switch (lParam) {782case 0: return (u32)(u64)Memory::base;783case 1: return (u32)((u64)Memory::base >> 32);784case 2: return (u32)(u64)(&Memory::base);785case 3: return (u32)((u64)(&Memory::base) >> 32);786default:787return 0;788}789break;790791case WM_USER_GET_EMULATION_STATE:792return (u32)(Core_IsActive() && GetUIState() == UISTATE_INGAME);793794// Hack to kill the white line underneath the menubar.795// From https://stackoverflow.com/questions/57177310/how-to-paint-over-white-line-between-menu-bar-and-client-area-of-window796case WM_NCPAINT:797case WM_NCACTIVATE:798{799if (!IsDarkModeEnabled() || IsIconic(hWnd)) {800return DefWindowProc(hWnd, message, wParam, lParam);801}802803auto result = DefWindowProc(hWnd, message, wParam, lParam);804// Paint over the line with pure black. Could also try to figure out the dark theme color.805HDC hdc = GetWindowDC(hWnd);806RECT r = GetNonclientMenuBorderRect(hWnd);807HBRUSH red = CreateSolidBrush(RGB(0, 0, 0));808FillRect(hdc, &r, red);809DeleteObject(red);810ReleaseDC(hWnd, hdc);811return result;812}813814case WM_GETMINMAXINFO:815{816MINMAXINFO *minmax = reinterpret_cast<MINMAXINFO *>(lParam);817RECT rc = { 0 };818bool portrait = g_Config.IsPortrait();819rc.right = portrait ? 272 : 480;820rc.bottom = portrait ? 480 : 272;821AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, TRUE);822minmax->ptMinTrackSize.x = rc.right - rc.left;823minmax->ptMinTrackSize.y = rc.bottom - rc.top;824}825return 0;826827case WM_ACTIVATE:828{829UpdateWindowTitle();830bool pause = true;831if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) {832WindowsRawInput::GainFocus();833if (!IsIconic(GetHWND())) {834InputDevice::GainFocus();835}836g_activeWindow = WINDOW_MAINWINDOW;837pause = false;838} else {839g_activeWindow = WINDOW_OTHER;840}841if (!noFocusPause && g_Config.bPauseOnLostFocus && GetUIState() == UISTATE_INGAME) {842if (pause != Core_IsStepping()) {843if (disasmWindow)844SendMessage(disasmWindow->GetDlgHandle(), WM_COMMAND, IDC_STOPGO, 0);845else846Core_EnableStepping(pause, "ui.lost_focus", 0);847}848}849850if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) {851System_PostUIMessage(UIMessage::GOT_FOCUS);852hasFocus = true;853trapMouse = true;854}855if (wParam == WA_INACTIVE) {856System_PostUIMessage(UIMessage::LOST_FOCUS);857WindowsRawInput::LoseFocus();858InputDevice::LoseFocus();859hasFocus = false;860trapMouse = false;861}862}863break;864865case WM_SETFOCUS:866UpdateWindowTitle();867break;868869case WM_ERASEBKGND:870// This window is always covered by DisplayWindow. No reason to erase.871return 0;872873case WM_MOVE:874SavePosition();875break;876877case WM_ENTERSIZEMOVE:878inResizeMove = true;879break;880881case WM_EXITSIZEMOVE:882inResizeMove = false;883HandleSizeChange(SIZE_RESTORED);884break;885886case WM_SIZE:887switch (wParam) {888case SIZE_RESTORED:889case SIZE_MAXIMIZED:890if (g_IgnoreWM_SIZE) {891return DefWindowProc(hWnd, message, wParam, lParam);892} else if (!inResizeMove) {893HandleSizeChange(wParam);894}895if (hasFocus) {896InputDevice::GainFocus();897}898break;899900case SIZE_MINIMIZED:901Core_NotifyWindowHidden(true);902if (!g_Config.bPauseWhenMinimized) {903System_PostUIMessage(UIMessage::WINDOW_MINIMIZED, "true");904}905InputDevice::LoseFocus();906break;907default:908break;909}910break;911912// Wheel events have to stay in WndProc for compatibility with older Windows(7). See #12156913case WM_MOUSEWHEEL:914{915int wheelDelta = (short)(wParam >> 16);916KeyInput key;917key.deviceId = DEVICE_ID_MOUSE;918919if (wheelDelta < 0) {920key.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN;921wheelDelta = -wheelDelta;922} else {923key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP;924}925// There's no release event, but we simulate it in NativeKey/NativeFrame.926key.flags = KEY_DOWN | KEY_HASWHEELDELTA | (wheelDelta << 16);927NativeKey(key);928}929break;930931case WM_TIMER:932// Hack: Take the opportunity to also show/hide the mouse cursor in fullscreen mode.933switch (wParam) {934case TIMER_CURSORUPDATE:935CorrectCursor();936return 0;937938case TIMER_CURSORMOVEUPDATE:939hideCursor = true;940KillTimer(hWnd, TIMER_CURSORMOVEUPDATE);941return 0;942}943break;944945case WM_COMMAND:946{947if (!MainThread_Ready())948return DefWindowProc(hWnd, message, wParam, lParam);949950MainWindowMenu_Process(hWnd, wParam);951}952break;953954case WM_USER_TOGGLE_FULLSCREEN:955ToggleFullscreen(hwndMain, wParam ? true : false);956break;957958case WM_INPUT:959return WindowsRawInput::Process(hWnd, wParam, lParam);960961// TODO: Could do something useful with WM_INPUT_DEVICE_CHANGE?962963// Not sure why we are actually getting WM_CHAR even though we use RawInput, but alright..964case WM_CHAR:965return WindowsRawInput::ProcessChar(hWnd, wParam, lParam);966967case WM_DEVICECHANGE:968#ifndef _M_ARM969DinputDevice::CheckDevices();970#endif971if (winCamera)972winCamera->CheckDevices();973if (winMic)974winMic->CheckDevices();975return DefWindowProc(hWnd, message, wParam, lParam);976977case WM_VERYSLEEPY_MSG:978switch (wParam) {979case VERYSLEEPY_WPARAM_SUPPORTED:980return TRUE;981982case VERYSLEEPY_WPARAM_GETADDRINFO:983{984VerySleepy_AddrInfo *info = (VerySleepy_AddrInfo *)lParam;985const u8 *ptr = (const u8 *)info->addr;986std::string name;987988std::lock_guard<std::recursive_mutex> guard(MIPSComp::jitLock);989if (MIPSComp::jit && MIPSComp::jit->DescribeCodePtr(ptr, name)) {990swprintf_s(info->name, L"Jit::%S", name.c_str());991return TRUE;992}993if (gpu && gpu->DescribeCodePtr(ptr, name)) {994swprintf_s(info->name, L"GPU::%S", name.c_str());995return TRUE;996}997}998return FALSE;9991000default:1001return FALSE;1002}1003break;10041005case WM_DROPFILES:1006{1007if (!MainThread_Ready())1008return DefWindowProc(hWnd, message, wParam, lParam);10091010HDROP hdrop = (HDROP)wParam;1011int count = DragQueryFile(hdrop, 0xFFFFFFFF, 0, 0);1012if (count != 1) {1013// TODO: Translate? Or just not bother?1014MessageBox(hwndMain, L"You can only load one file at a time", L"Error", MB_ICONINFORMATION);1015} else {1016TCHAR filename[1024];1017if (DragQueryFile(hdrop, 0, filename, ARRAY_SIZE(filename)) != 0) {1018const std::string utf8_filename = ReplaceAll(ConvertWStringToUTF8(filename), "\\", "/");1019System_PostUIMessage(UIMessage::REQUEST_GAME_BOOT, utf8_filename);1020Core_EnableStepping(false);1021}1022}1023DragFinish(hdrop);1024}1025break;10261027case WM_CLOSE:1028InputDevice::StopPolling();1029MainThread_Stop();1030WindowsRawInput::Shutdown();1031return DefWindowProc(hWnd,message,wParam,lParam);10321033case WM_DESTROY:1034KillTimer(hWnd, TIMER_CURSORUPDATE);1035KillTimer(hWnd, TIMER_CURSORMOVEUPDATE);1036// Main window is gone, this tells the message loop to exit.1037PostQuitMessage(0);1038return 0;10391040case WM_USER + 1:1041NotifyDebuggerMapLoaded();1042if (disasmWindow)1043disasmWindow->UpdateDialog();1044break;10451046case WM_USER_SAVESTATE_FINISH:1047SetCursor(LoadCursor(0, IDC_ARROW));1048break;10491050case WM_USER_UPDATE_UI:1051TranslateMenus(hwndMain, menu);1052// Update checked status immediately for accelerators.1053UpdateMenus();1054break;10551056case WM_USER_WINDOW_TITLE_CHANGED:1057UpdateWindowTitle();1058break;10591060case WM_USER_RESTART_EMUTHREAD:1061NativeSetRestarting();1062InputDevice::StopPolling();1063MainThread_Stop();1064coreState = CORE_POWERUP;1065UpdateUIState(UISTATE_MENU);1066MainThread_Start(g_Config.iGPUBackend == (int)GPUBackend::OPENGL);1067InputDevice::BeginPolling();1068break;10691070case WM_USER_SWITCHUMD_UPDATED:1071UpdateSwitchUMD();1072break;10731074case WM_MENUSELECT:1075// Called when a menu is opened. Also when an item is selected, but meh.1076UpdateMenus(true);1077WindowsRawInput::NotifyMenu();1078trapMouse = false;1079break;10801081case WM_EXITMENULOOP:1082// Called when menu is closed.1083trapMouse = true;1084break;10851086// Turn off the screensaver if in-game.1087// Note that if there's a screensaver password, this simple method1088// doesn't work on Vista or higher.1089case WM_SYSCOMMAND:1090// Disable Alt key for menu if it's been mapped.1091if (wParam == SC_KEYMENU && (lParam >> 16) <= 0) {1092if (KeyMap::IsKeyMapped(DEVICE_ID_KEYBOARD, NKCODE_ALT_LEFT) || KeyMap::IsKeyMapped(DEVICE_ID_KEYBOARD, NKCODE_ALT_RIGHT)) {1093return 0;1094}1095}1096if (g_keepScreenBright) {1097switch (wParam) {1098case SC_SCREENSAVE:1099return 0;1100case SC_MONITORPOWER:1101if (lParam == 1 || lParam == 2) {1102return 0;1103} else {1104break;1105}1106default:1107// fall down to DefWindowProc1108break;1109}1110}1111return DefWindowProc(hWnd, message, wParam, lParam);1112case WM_SETTINGCHANGE:1113{1114if (g_darkModeSupported && IsColorSchemeChangeMessage(lParam))1115SendMessageW(hWnd, WM_THEMECHANGED, 0, 0);1116}1117return DefWindowProc(hWnd, message, wParam, lParam);11181119case WM_THEMECHANGED:1120{1121if (g_darkModeSupported)1122{1123_AllowDarkModeForWindow(hWnd, g_darkModeEnabled);1124RefreshTitleBarThemeColor(hWnd);1125}1126return DefWindowProc(hWnd, message, wParam, lParam);1127}11281129default:1130return DefWindowProc(hWnd, message, wParam, lParam);1131}1132return 0;1133}11341135void Redraw() {1136InvalidateRect(hwndDisplay,0,0);1137}11381139HINSTANCE GetHInstance() {1140return hInst;1141}11421143void ToggleDebugConsoleVisibility() {1144if (!g_Config.bEnableLogging) {1145LogManager::GetInstance()->GetConsoleListener()->Show(false);1146EnableMenuItem(menu, ID_DEBUG_LOG, MF_GRAYED);1147}1148else {1149LogManager::GetInstance()->GetConsoleListener()->Show(true);1150EnableMenuItem(menu, ID_DEBUG_LOG, MF_ENABLED);1151}1152}11531154void SendToggleFullscreen(bool fullscreen) {1155PostMessage(hwndMain, WM_USER_TOGGLE_FULLSCREEN, fullscreen, 0);1156}11571158bool IsFullscreen() {1159return g_isFullscreen;1160}11611162void RunCallbackInWndProc(void (*callback)(void *, void *), void *userdata) {1163PostMessage(hwndMain, WM_USER_RUN_CALLBACK, reinterpret_cast<WPARAM>(callback), reinterpret_cast<LPARAM>(userdata));1164}11651166} // namespace116711681169