// 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 #include #include #include #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 "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/MIPS/JitCommon/JitBlockCache.h" #include "Core/Reporting.h" #include "Windows/InputBox.h" #include "Windows/InputDevice.h" #if PPSSPP_API(ANY_GL) #include "Windows/GPU/WindowsGLContext.h" #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/LogManager.h" #include "Common/ConsoleListener.h" #include "Windows/W32Util/DialogManager.h" #include "Windows/W32Util/ShellUtil.h" #include "Windows/W32Util/Misc.h" #include "Windows/RawInput.h" #include "Windows/CaptureDevice.h" #include "Windows/TouchInputHandler.h" #include "Windows/MainWindowMenu.h" #include "GPU/GPUInterface.h" #include "UI/OnScreenDisplay.h" #include "UI/GameSettingsScreen.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 // 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::wstring windowTitle; #define TIMER_CURSORUPDATE 1 #define TIMER_CURSORMOVEUPDATE 2 #define TIMER_WHEELRELEASE 3 #define CURSORUPDATE_INTERVAL_MS 1000 #define CURSORUPDATE_MOVE_TIMESPAN_MS 500 #define WHEELRELEASE_DELAY_MS 16 namespace MainWindow { HWND hwndMain; HWND hwndDisplay; HWND hwndGameList; TouchInputHandler touchHandler; static HMENU menu; HINSTANCE hInst; static int cursorCounter = 0; static int prevCursorX = -1; static int prevCursorY = -1; static bool mouseButtonDown = false; static bool hideCursor = false; static int g_WindowState; static bool g_IgnoreWM_SIZE = false; static bool inFullscreenResize = false; static bool inResizeMove = false; static bool hasFocus = true; static bool g_isFullscreen = false; static bool disasmMapLoadPending = false; static bool memoryMapLoadPending = false; // gross hack bool noFocusPause = false; // TOGGLE_PAUSE state to override pause on lost focus bool trapMouse = true; // Handles some special cases(alt+tab, win menu) when game is running and mouse is confined #define MAX_LOADSTRING 100 const TCHAR *szWindowClass = TEXT("PPSSPPWnd"); const TCHAR *szDisplayClass = TEXT("PPSSPPDisplay"); // Forward declarations of functions included in this code module: LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK DisplayProc(HWND, UINT, WPARAM, LPARAM); HWND GetHWND() { return hwndMain; } HWND GetDisplayHWND() { return hwndDisplay; } void Init(HINSTANCE hInstance) { // Register classes - Main Window WNDCLASSEX wcex; memset(&wcex, 0, sizeof(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 = NULL; // Always covered by display window wcex.lpszMenuName = (LPCWSTR)IDR_MENU1; 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); WNDCLASSEX wcdisp; memset(&wcdisp, 0, sizeof(wcdisp)); // Display Window (contained in main window) wcdisp.cbSize = sizeof(WNDCLASSEX); wcdisp.style = CS_HREDRAW | CS_VREDRAW; wcdisp.lpfnWndProc = (WNDPROC)DisplayProc; wcdisp.hInstance = hInstance; wcdisp.hCursor = LoadCursor(NULL, IDC_ARROW); wcdisp.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wcdisp.lpszMenuName = 0; wcdisp.lpszClassName = szDisplayClass; wcdisp.hIcon = 0; wcdisp.hIconSm = 0; RegisterClassEx(&wcdisp); } void SavePosition() { if (g_Config.UseFullScreen() || inFullscreenResize) return; WINDOWPLACEMENT placement{}; GetWindowPlacement(hwndMain, &placement); if (placement.showCmd == SW_SHOWNORMAL) { RECT rc; GetWindowRect(hwndMain, &rc); g_Config.iWindowX = rc.left; g_Config.iWindowY = rc.top; g_Config.iWindowWidth = rc.right - rc.left; g_Config.iWindowHeight = rc.bottom - rc.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"); // Actually, auto mode should be more granular... int width, height; if (g_Config.IsPortrait()) { 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("gpu_renderResized", ""); } void CorrectCursor() { bool autoHide = ((g_Config.UseFullScreen() && !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(hwndDisplay, &rc); ClientToScreen(hwndDisplay, reinterpret_cast(&rc.left)); ClientToScreen(hwndDisplay, reinterpret_cast(&rc.right)); ClipCursor(&rc); } } else { hideCursor = !autoHide; if (cursorCounter < 0) { cursorCounter = ShowCursor(TRUE); SetCursor(LoadCursor(NULL, IDC_ARROW)); ClipCursor(NULL); } } } void ReleaseMouseWheel() { // For simplicity release both wheel events KeyInput key; key.deviceId = DEVICE_ID_MOUSE; key.flags = KEY_UP; key.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN; NativeKey(key); key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP; NativeKey(key); } static void HandleSizeChange(int newSizingType) { SavePosition(); Core_NotifyWindowHidden(false); if (!g_Config.bPauseWhenMinimized) { System_PostUIMessage("window minimized", "false"); } int width = 0, height = 0; RECT rc; GetClientRect(hwndMain, &rc); width = rc.right - rc.left; height = rc.bottom - rc.top; // Moves the internal display window to match the inner size of the main window. MoveWindow(hwndDisplay, 0, 0, width, height, TRUE); // 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(SYSTEM, "Pixel width/height: %dx%d", PSP_CoreParameter().pixelWidth, PSP_CoreParameter().pixelHeight); if (UpdateScreenScale(width, height)) { System_PostUIMessage("gpu_displayResized", ""); System_PostUIMessage("gpu_renderResized", ""); } // Don't save the window state if fullscreen. if (!g_Config.UseFullScreen()) { g_WindowState = newSizingType; } } void ToggleFullscreen(HWND hWnd, bool goingFullscreen) { GraphicsContext *graphicsContext = PSP_CoreParameter().graphicsContext; // Make sure no rendering is happening during the switch. if (graphicsContext) { graphicsContext->Pause(); } WINDOWPLACEMENT placement = { sizeof(WINDOWPLACEMENT) }; GetWindowPlacement(hwndMain, &placement); int oldWindowState = g_WindowState; inFullscreenResize = true; g_IgnoreWM_SIZE = true; DWORD dwStyle; if (!goingFullscreen) { dwStyle = ::GetWindowLong(hWnd, GWL_STYLE); // Remove popup dwStyle &= ~WS_POPUP; // Re-add caption and border styles. dwStyle |= WS_OVERLAPPEDWINDOW; } else { // If the window was maximized before going fullscreen, make sure to restore first // in order not to have the taskbar show up on top of PPSSPP. if (oldWindowState == SIZE_MAXIMIZED || placement.showCmd == SW_SHOWMAXIMIZED) { ShowWindow(hwndMain, SW_RESTORE); } // Remove caption and border styles. dwStyle = ::GetWindowLong(hWnd, GWL_STYLE); dwStyle &= ~WS_OVERLAPPEDWINDOW; // Add Popup dwStyle |= WS_POPUP; } ::SetWindowLong(hWnd, GWL_STYLE, dwStyle); // Remove the menu bar. This can trigger WM_SIZE because the contents change size. ::SetMenu(hWnd, goingFullscreen || !g_Config.bShowMenuBar ? NULL : menu); if (g_Config.UseFullScreen() != goingFullscreen) { g_Config.bFullScreen = goingFullscreen; g_Config.iForceFullScreen = -1; } g_isFullscreen = goingFullscreen; g_IgnoreWM_SIZE = false; // Resize to the appropriate view. // If we're returning to window mode, re-apply the appropriate size setting. if (goingFullscreen) { if (g_Config.bFullScreenMulti) { // Maximize isn't enough to display on all monitors. // Remember that negative coordinates may be valid. int totalX = GetSystemMetrics(SM_XVIRTUALSCREEN); int totalY = GetSystemMetrics(SM_YVIRTUALSCREEN); int totalWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); int totalHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); MoveWindow(hwndMain, totalX, totalY, totalWidth, totalHeight, TRUE); HandleSizeChange(oldWindowState); ShowWindow(hwndMain, SW_SHOW); } else { ShowWindow(hwndMain, SW_MAXIMIZE); } } else { ShowWindow(hwndMain, oldWindowState == SIZE_MAXIMIZED ? SW_MAXIMIZE : SW_RESTORE); if (g_Config.bFullScreenMulti && oldWindowState != SIZE_MAXIMIZED) { // Return the screen to where it was. MoveWindow(hwndMain, g_Config.iWindowX, g_Config.iWindowY, g_Config.iWindowWidth, g_Config.iWindowHeight, TRUE); } if (oldWindowState == SIZE_MAXIMIZED) { // WM_SIZE wasn't sent, since the size didn't change (it was full screen before and after.) HandleSizeChange(oldWindowState); } } inFullscreenResize = false; CorrectCursor(); ShowOwnedPopups(hwndMain, goingFullscreen ? FALSE : TRUE); W32Util::MakeTopMost(hwndMain, g_Config.bTopMost); WindowsRawInput::NotifyMenu(); if (graphicsContext) { graphicsContext->Resume(); } } void Minimize() { ShowWindow(hwndMain, SW_MINIMIZE); InputDevice::LoseFocus(); } RECT DetermineWindowRectangle() { 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.UseFullScreen()) { 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) { bool portrait = g_Config.IsPortrait(); // 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 = 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) { windowTitle = title; } BOOL Show(HINSTANCE hInstance) { hInst = hInstance; // Store instance handle in our global variable. RECT rc = DetermineWindowRectangle(); u32 style = WS_OVERLAPPEDWINDOW; hwndMain = CreateWindowEx(0,szWindowClass, L"", style, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInstance, NULL); if (!hwndMain) return FALSE; SetWindowLong(hwndMain, GWL_EXSTYLE, WS_EX_APPWINDOW); RECT rcClient; GetClientRect(hwndMain, &rcClient); hwndDisplay = CreateWindowEx(0, szDisplayClass, L"", WS_CHILD | WS_VISIBLE, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, hwndMain, 0, hInstance, 0); if (!hwndDisplay) return FALSE; menu = GetMenu(hwndMain); MENUINFO info; ZeroMemory(&info,sizeof(MENUINFO)); info.cbSize = sizeof(MENUINFO); info.cyMax = 0; info.dwStyle = MNS_CHECKORBMP; info.fMask = MIM_STYLE; for (int i = 0; i < GetMenuItemCount(menu); i++) { SetMenuInfo(GetSubMenu(menu,i), &info); } // Always translate first: translating resets the menu. TranslateMenus(hwndMain, menu); UpdateMenus(); // Accept dragged files. DragAcceptFiles(hwndMain, TRUE); hideCursor = true; SetTimer(hwndMain, TIMER_CURSORUPDATE, CURSORUPDATE_INTERVAL_MS, 0); ToggleFullscreen(hwndMain, g_Config.UseFullScreen()); W32Util::MakeTopMost(hwndMain, g_Config.bTopMost); touchHandler.registerTouchWindow(hwndDisplay); WindowsRawInput::Init(); SetFocus(hwndMain); 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); if (disasmWindow) delete disasmWindow; disasmWindow = nullptr; #if PPSSPP_API(ANY_GL) DialogManager::RemoveDlg(geDebuggerWindow); if (geDebuggerWindow) delete geDebuggerWindow; geDebuggerWindow = nullptr; #endif DialogManager::RemoveDlg(memoryWindow); if (memoryWindow) delete memoryWindow; memoryWindow = nullptr; DialogManager::RemoveDlg(vfpudlg); if (vfpudlg) delete vfpudlg; vfpudlg = nullptr; } LRESULT CALLBACK DisplayProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { static bool firstErase = true; switch (message) { case WM_ACTIVATE: if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) { g_activeWindow = WINDOW_MAINWINDOW; } break; case WM_SIZE: break; case WM_SETFOCUS: 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; // 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; float x = GET_X_LPARAM(lParam) * g_display.dpi_scale_x; float y = GET_Y_LPARAM(lParam) * g_display.dpi_scale_y; WindowsRawInput::SetMousePos(x, y); TouchInput touch; touch.id = 0; touch.flags = TOUCH_DOWN; touch.x = x; touch.y = y; NativeTouch(touch); SetCapture(hWnd); // Simulate doubleclick, doesn't work with RawInput enabled static double lastMouseDown; static float lastMouseDownX = -1.0f; static float lastMouseDownY = -1.0f; double now = time_now_d(); if ((now - lastMouseDown) < 0.001 * GetDoubleClickTime()) { float dx = lastMouseDownX - x; float dy = lastMouseDownX - x; float distSq = dx * dx + dy * dy; if (distSq < 3.0f*3.0f && !g_Config.bShowTouchControls && !g_Config.bMouseControl && GetUIState() == UISTATE_INGAME && g_Config.bFullscreenOnDoubleclick) { SendToggleFullscreen(!g_Config.UseFullScreen()); } lastMouseDown = 0.0; } else { lastMouseDown = 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); if (abs(cursorX - prevCursorX) > 1 || abs(cursorY - prevCursorY) > 1) { hideCursor = false; SetTimer(hwndMain, TIMER_CURSORMOVEUPDATE, CURSORUPDATE_MOVE_TIMESPAN_MS, 0); } prevCursorX = cursorX; prevCursorY = cursorY; float x = (float)cursorX * g_display.dpi_scale_x; float y = (float)cursorY * g_display.dpi_scale_y; WindowsRawInput::SetMousePos(x, y); if (wParam & MK_LBUTTON) { TouchInput touch; touch.id = 0; touch.flags = TOUCH_MOVE; 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; float x = (float)GET_X_LPARAM(lParam) * g_display.dpi_scale_x; float y = (float)GET_Y_LPARAM(lParam) * g_display.dpi_scale_y; WindowsRawInput::SetMousePos(x, y); TouchInput touch; touch.id = 0; touch.flags = TOUCH_UP; touch.x = x; touch.y = y; NativeTouch(touch); ReleaseCapture(); } break; case WM_TOUCH: touchHandler.handleTouchEvent(hWnd, message, wParam, lParam); return 0; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } RECT MapRectFromClientToWndCoords(HWND hwnd, const RECT & r) { RECT wnd_coords = r; // map to screen MapWindowPoints(hwnd, NULL, reinterpret_cast(&wnd_coords), 2); RECT scr_coords; GetWindowRect(hwnd, &scr_coords); // map to window coords by substracting the window coord origin in // screen coords. OffsetRect(&wnd_coords, -scr_coords.left, -scr_coords.top); return wnd_coords; } RECT GetNonclientMenuBorderRect(HWND hwnd) { RECT r; GetClientRect(hwnd, &r); r = MapRectFromClientToWndCoords(hwnd, r); int y = r.top - 1; return { r.left, y, r.right, y + 1 }; } LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { LRESULT darkResult = 0; if (UAHDarkModeWndProc(hWnd, message, wParam, lParam, &darkResult)) { return darkResult; } switch (message) { case WM_CREATE: if (!DoesVersionMatchWindows(6, 0, 0, 0, true)) { // 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); } 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); // 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 = 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(lParam); RECT rc = { 0 }; bool portrait = g_Config.IsPortrait(); 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())) { InputDevice::GainFocus(); } g_activeWindow = WINDOW_MAINWINDOW; pause = false; } if (!noFocusPause && g_Config.bPauseOnLostFocus && GetUIState() == UISTATE_INGAME) { if (pause != Core_IsStepping()) { // != is xor for bools if (disasmWindow) SendMessage(disasmWindow->GetDlgHandle(), WM_COMMAND, IDC_STOPGO, 0); else Core_EnableStepping(pause, "ui.lost_focus", 0); } } if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) { System_PostUIMessage("got_focus", ""); hasFocus = true; trapMouse = true; } if (wParam == WA_INACTIVE) { System_PostUIMessage("lost_focus", ""); WindowsRawInput::LoseFocus(); InputDevice::LoseFocus(); hasFocus = false; trapMouse = false; } } break; case WM_SETFOCUS: UpdateWindowTitle(); break; case WM_ERASEBKGND: // This window is always covered by DisplayWindow. No reason to erase. return 0; case WM_MOVE: SavePosition(); break; case WM_ENTERSIZEMOVE: inResizeMove = true; break; case WM_EXITSIZEMOVE: inResizeMove = false; HandleSizeChange(SIZE_RESTORED); break; case WM_SIZE: switch (wParam) { case SIZE_RESTORED: case SIZE_MAXIMIZED: if (g_IgnoreWM_SIZE) { return DefWindowProc(hWnd, message, wParam, lParam); } else if (!inResizeMove) { HandleSizeChange(wParam); } if (hasFocus) { InputDevice::GainFocus(); } break; case SIZE_MINIMIZED: Core_NotifyWindowHidden(true); if (!g_Config.bPauseWhenMinimized) { System_PostUIMessage("window minimized", "true"); } InputDevice::LoseFocus(); break; default: break; } 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 separate keyup event for mousewheel events, // so we release it with a slight delay. key.flags = KEY_DOWN | KEY_HASWHEELDELTA | (wheelDelta << 16); SetTimer(hwndMain, TIMER_WHEELRELEASE, WHEELRELEASE_DELAY_MS, 0); 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; // Hack: need to release wheel event with a delay for games to register it was "pressed down". case TIMER_WHEELRELEASE: ReleaseMouseWheel(); KillTimer(hWnd, TIMER_WHEELRELEASE); return 0; } break; case WM_COMMAND: { if (!MainThread_Ready()) return DefWindowProc(hWnd, message, wParam, lParam); MainWindowMenu_Process(hWnd, wParam); } break; case WM_USER_TOGGLE_FULLSCREEN: ToggleFullscreen(hwndMain, wParam ? true : false); 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 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); HDROP hdrop = (HDROP)wParam; int count = DragQueryFile(hdrop,0xFFFFFFFF,0,0); if (count != 1) { MessageBox(hwndMain,L"You can only load one file at a time",L"Error",MB_ICONINFORMATION); } else { TCHAR filename[512]; if (DragQueryFile(hdrop, 0, filename, 512) != 0) { const std::string utf8_filename = ReplaceAll(ConvertWStringToUTF8(filename), "\\", "/"); System_PostUIMessage("boot", utf8_filename.c_str()); Core_EnableStepping(false); } } } break; case WM_CLOSE: InputDevice::StopPolling(); MainThread_Stop(); WindowsRawInput::Shutdown(); return DefWindowProc(hWnd,message,wParam,lParam); case WM_DESTROY: KillTimer(hWnd, TIMER_CURSORUPDATE); KillTimer(hWnd, TIMER_CURSORMOVEUPDATE); KillTimer(hWnd, TIMER_WHEELRELEASE); // 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, menu); // Update checked status immediately for accelerators. UpdateMenus(); break; case WM_USER_WINDOW_TITLE_CHANGED: UpdateWindowTitle(); break; case WM_USER_RESTART_EMUTHREAD: NativeSetRestarting(); InputDevice::StopPolling(); MainThread_Stop(); coreState = CORE_POWERUP; UpdateUIState(UISTATE_MENU); MainThread_Start(g_Config.iGPUBackend == (int)GPUBackend::OPENGL); InputDevice::BeginPolling(); break; case WM_USER_SWITCHUMD_UPDATED: UpdateSwitchUMD(); break; case WM_MENUSELECT: // Called when a menu is opened. Also when an item is selected, but meh. UpdateMenus(true); WindowsRawInput::NotifyMenu(); trapMouse = false; break; case WM_EXITMENULOOP: // Called when menu is closed. trapMouse = true; break; // Turn off the screensaver. // Note that if there's a screensaver password, this simple method // doesn't work on Vista or higher. case WM_SYSCOMMAND: { switch (wParam) { case SC_SCREENSAVE: return 0; case SC_MONITORPOWER: return 0; } return DefWindowProc(hWnd, message, wParam, lParam); } break; 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); } default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } void Redraw() { InvalidateRect(hwndDisplay,0,0); } HINSTANCE GetHInstance() { return hInst; } void ToggleDebugConsoleVisibility() { if (!g_Config.bEnableLogging) { LogManager::GetInstance()->GetConsoleListener()->Show(false); EnableMenuItem(menu, ID_DEBUG_LOG, MF_GRAYED); } else { LogManager::GetInstance()->GetConsoleListener()->Show(true); EnableMenuItem(menu, ID_DEBUG_LOG, MF_ENABLED); } } void SendToggleFullscreen(bool fullscreen) { PostMessage(hwndMain, WM_USER_TOGGLE_FULLSCREEN, fullscreen, 0); } bool IsFullscreen() { return g_isFullscreen; } } // namespace