Rework fullscreen on Windows based on tests

Test implementation in https://github.com/hrydgard/fullscreen-test
This commit is contained in:
Henrik Rydgård committed 2026-02-02 10:51:51 +01:00
1 parent 7c2632cd55
commit d331af58ab
2 files changed
+128 -89

No files matched your search

+127 -88
View File
@@ -150,8 +150,6 @@ namespace MainWindow {
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;
@@ -159,7 +157,7 @@ namespace MainWindow {
static bool disasmMapLoadPending = false;
static bool memoryMapLoadPending = false;
static bool wasMinimized = false;
static bool g_wasMinimized = false;
// gross hack
bool noFocusPause = false; // TOGGLE_PAUSE state to override pause on lost focus
@@ -193,12 +191,14 @@ namespace MainWindow {
}
void SavePosition() {
if (g_Config.bFullScreen || inFullscreenResize)
return;
WINDOWPLACEMENT placement{};
GetWindowPlacement(hwndMain, &placement);
g_Config.iWindowSizeState = (int)ShowCmdToWindowSizeState(placement.showCmd);
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;
@@ -216,6 +216,10 @@ namespace MainWindow {
void SetWindowSize(int zoom) {
AssertCurrentThreadName("Main");
if (g_Config.bFullScreen || inFullscreenResize)
return;
// Actually, auto mode should be more granular...
int width, height;
const DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());
@@ -241,7 +245,7 @@ namespace MainWindow {
}
void CorrectCursor() {
bool autoHide = ((g_Config.bFullScreen && !mouseButtonDown) || (g_Config.bMouseControl && trapMouse)) && GetUIState() == UISTATE_INGAME;
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);
@@ -264,7 +268,6 @@ namespace MainWindow {
}
static void HandleSizeChange(int newSizingType) {
SavePosition();
Native_NotifyWindowHidden(false);
if (!g_Config.bPauseWhenMinimized) {
System_PostUIMessage(UIMessage::WINDOW_MINIMIZED, "false");
@@ -287,91 +290,82 @@ namespace MainWindow {
System_PostUIMessage(UIMessage::GPU_DISPLAY_RESIZED);
System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED);
}
// Don't save the window state if fullscreen.
if (!g_Config.bFullScreen) {
g_WindowState = newSizingType;
}
}
void ToggleFullscreen(HWND hWnd, bool goingFullscreen) {
void ApplyFullscreenState(HWND hWnd, bool goFullscreen) {
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);
const DWORD prevStyle = GetWindowLong(hWnd, GWL_STYLE);
const bool isCurrentlyFullscreen = !(prevStyle & WS_OVERLAPPEDWINDOW);
const 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);
if (goFullscreen && !isCurrentlyFullscreen) {
// 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;
wp.showCmd = SW_SHOW;
}
// 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 if going fullscreen. This can trigger WM_SIZE because the contents change size.
::SetMenu(hWnd, goingFullscreen || !g_Config.bShowMenuBar ? NULL : g_hMenu);
g_Config.bFullScreen = 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) {
SetMenu(hWnd, NULL);
// Strip all decorations
SetWindowLong(hWnd, GWL_STYLE, prevStyle & ~WS_OVERLAPPEDWINDOW);
// 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);
MoveWindow(hwndMain, totalX, totalY, totalWidth, totalHeight, TRUE);
HandleSizeChange(oldWindowState);
ShowWindow(hwndMain, SW_SHOW);
SetWindowPos(hWnd, HWND_TOP,
totalX, totalY,
totalWidth, totalHeight,
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
g_Config.bFullScreen = true;
} 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);
MONITORINFO mi = {sizeof(mi)};
if (GetMonitorInfo(MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST), &mi)) {
SetMenu(hWnd, NULL);
// Strip all decorations
SetWindowLong(hWnd, GWL_STYLE, prevStyle & ~WS_OVERLAPPEDWINDOW);
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);
g_Config.bFullScreen = true;
}
}
} else if (!goFullscreen && isCurrentlyFullscreen) {
// Transitioning to Windowed
SetWindowLong(hWnd, GWL_STYLE, prevStyle | WS_OVERLAPPEDWINDOW);
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);
g_Config.bFullScreen = false;
}
inFullscreenResize = false;
CorrectCursor();
ShowOwnedPopups(hwndMain, goingFullscreen ? FALSE : TRUE);
ShowOwnedPopups(hwndMain, goFullscreen ? FALSE : TRUE);
W32Util::MakeTopMost(hwndMain, g_Config.bTopMost);
WindowsRawInput::NotifyMenu();
@@ -386,7 +380,8 @@ namespace MainWindow {
g_InputManager.LoseFocus();
}
RECT DetermineWindowRectangle() {
// TODO: Currently unused.
RECT DetermineDefaultWindowRectangle() {
const int virtualScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
const int virtualScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
const int virtualScreenX = GetSystemMetrics(SM_XVIRTUALSCREEN);
@@ -502,7 +497,7 @@ namespace MainWindow {
hideCursor = true;
SetTimer(hwndMain, TIMER_CURSORUPDATE, CURSORUPDATE_INTERVAL_MS, 0);
ToggleFullscreen(hwndMain, g_Config.bFullScreen);
ApplyFullscreenState(hwndMain, g_Config.bFullScreen);
W32Util::MakeTopMost(hwndMain, g_Config.bTopMost);
@@ -804,10 +799,61 @@ namespace MainWindow {
// Then never erase, let the OpenGL drawing take care of everything.
return 1;
case WM_MOVE:
SavePosition();
case WM_USER_TOGGLE_FULLSCREEN:
ApplyFullscreenState(hwndMain, wParam ? true : false);
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:
{
const WINDOWPOS *pos = reinterpret_cast<WINDOWPOS*>(lParam);
if (!pos) {
// Uh?
return DefWindowProc(hWnd, message, wParam, lParam);
}
const bool sizeChanged = !(pos->flags & SWP_NOSIZE);
if (sizeChanged) {
if (g_Config.bFullScreen) {
MONITORINFO mi = {sizeof(mi)};
if (GetMonitorInfo(MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST), &mi)) {
int monWidth = mi.rcMonitor.right - mi.rcMonitor.left;
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) {
SetMenu(hWnd, g_hMenu);
}
DWORD style = GetWindowLong(hWnd, GWL_STYLE);
if (!(style & WS_CAPTION)) {
SetWindowLong(hWnd, GWL_STYLE, style | WS_OVERLAPPEDWINDOW);
SetWindowPos(hWnd, NULL, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE);
}
}
}
}
// This means that WM_SIZE and WM_MOVE won't be sent.
HandleSizeChange(SIZE_RESTORED);
}
return 0;
}
case WM_ENTERSIZEMOVE:
inResizeMove = true;
break;
@@ -821,17 +867,12 @@ namespace MainWindow {
switch (wParam) {
case SIZE_RESTORED:
case SIZE_MAXIMIZED:
if (g_IgnoreWM_SIZE) {
return DefWindowProc(hWnd, message, wParam, lParam);
} else if (!inResizeMove) {
if (!inResizeMove) {
HandleSizeChange(wParam);
}
if (hasFocus) {
g_InputManager.GainFocus();
}
if (wasMinimized) {
if (g_wasMinimized) {
System_PostUIMessage(UIMessage::WINDOW_RESTORED, "true");
wasMinimized = false;
g_wasMinimized = false;
}
break;
@@ -840,13 +881,11 @@ namespace MainWindow {
if (!g_Config.bPauseWhenMinimized) {
System_PostUIMessage(UIMessage::WINDOW_MINIMIZED, "true");
}
g_InputManager.LoseFocus();
wasMinimized = true;
g_wasMinimized = true;
break;
default:
break;
}
SavePosition();
break;
// Wheel events have to stay in WndProc for compatibility with older Windows(7). See #12156
@@ -879,6 +918,9 @@ namespace MainWindow {
hideCursor = true;
KillTimer(hWnd, TIMER_CURSORMOVEUPDATE);
return 0;
default:
break;
}
break;
@@ -891,10 +933,6 @@ namespace MainWindow {
}
break;
case WM_USER_TOGGLE_FULLSCREEN:
ToggleFullscreen(hwndMain, wParam ? true : false);
break;
case WM_INPUT:
return WindowsRawInput::Process(hWnd, wParam, lParam);
@@ -972,6 +1010,7 @@ namespace MainWindow {
}
case WM_DESTROY:
SavePosition();
g_InputManager.StopPolling();
g_InputManager.Shutdown();
WindowsRawInput::Shutdown();