From a2f6b94c527577b19e65175fe30679a9263d45bf Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Sat, 27 Jun 2026 21:04:55 +0200 Subject: [PATCH 1/7] feature(liveresize): integrate Alt+Enter borderless fullscreen and live resize support --- .../Include/Common/OptionPreferences.h | 2 + .../Include/GameClient/ControlBar.h | 7 +- .../Source/Common/OptionPreferences.cpp | 17 ++ .../GameClient/GUI/ControlBar/ControlBar.cpp | 5 + .../Source/WWVegas/WW3D2/dx8wrapper.cpp | 18 +- .../GameEngine/Include/Common/GlobalData.h | 4 +- .../Include/GameClient/GameWindowManager.h | 13 ++ .../GameEngine/Source/Common/CommandLine.cpp | 6 + .../GameEngine/Source/Common/GlobalData.cpp | 11 ++ .../GUI/GUICallbacks/Menus/OptionsMenu.cpp | 3 - .../GameClient/GUI/GameWindowManager.cpp | 50 +++++ .../GUI/GameWindowManagerScript.cpp | 34 ++++ .../GameEngine/Source/GameClient/InGameUI.cpp | 45 ++++- .../Win32Device/Common/Win32GameEngine.cpp | 3 + GeneralsMD/Code/Main/WinMain.cpp | 186 +++++++++++++++++- 15 files changed, 379 insertions(+), 25 deletions(-) diff --git a/Core/GameEngine/Include/Common/OptionPreferences.h b/Core/GameEngine/Include/Common/OptionPreferences.h index 85aba4228be..a83f01a6af9 100644 --- a/Core/GameEngine/Include/Common/OptionPreferences.h +++ b/Core/GameEngine/Include/Common/OptionPreferences.h @@ -104,6 +104,8 @@ class OptionPreferences : public UserPreferences Real getGammaValue(); Int getTextureReduction(); void getResolution(Int *xres, Int *yres); + Bool getWindowed() const; + void setWindowed(Bool windowed); Bool get3DShadowsEnabled(); Bool get2DShadowsEnabled(); Bool getCloudShadowsEnabled(); diff --git a/Core/GameEngine/Include/GameClient/ControlBar.h b/Core/GameEngine/Include/GameClient/ControlBar.h index 446277a13be..5ddc534fb7c 100644 --- a/Core/GameEngine/Include/GameClient/ControlBar.h +++ b/Core/GameEngine/Include/GameClient/ControlBar.h @@ -699,6 +699,10 @@ class ControlBar : public SubsystemInterface void showPurchaseScience(); void hidePurchaseScience(); void togglePurchaseScience(); + Bool isPurchaseScienceVisible() const; + + /// show rally point at world location, a nullptr location will hide any visible rally point marker + void showRallyPoint( const Coord3D *loc ); Bool hasAnyShortcutSelection() const; Bool canShowSpecialPowerShortcut() const; @@ -825,9 +829,6 @@ class ControlBar : public SubsystemInterface /// show/hide the portrait window image using the image from the object void setPortraitByObject( Object *obj ); - /// show rally point at world location, a nullptr location will hide any visible rally point marker - void showRallyPoint( const Coord3D *loc ); - /// post process step, after all commands and command sets are loaded void postProcessCommands(); diff --git a/Core/GameEngine/Source/Common/OptionPreferences.cpp b/Core/GameEngine/Source/Common/OptionPreferences.cpp index e681ef8b192..ff042cfae40 100644 --- a/Core/GameEngine/Source/Common/OptionPreferences.cpp +++ b/Core/GameEngine/Source/Common/OptionPreferences.cpp @@ -762,6 +762,23 @@ void OptionPreferences::getResolution(Int *xres, Int *yres) *yres=selectedYRes; } +Bool OptionPreferences::getWindowed() const +{ + OptionPreferences::const_iterator it = find("Windowed"); + if (it == end()) + return FALSE; + + if (stricmp(it->second.str(), "yes") == 0) { + return TRUE; + } + return FALSE; +} + +void OptionPreferences::setWindowed(Bool windowed) +{ + insert(std::make_pair(AsciiString("Windowed"), AsciiString(windowed ? "yes" : "no"))); +} + Real OptionPreferences::getMusicVolume() { OptionPreferences::const_iterator it = find("MusicVolume"); diff --git a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp index 697ee47a0a4..1d0800941c2 100644 --- a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp @@ -3016,6 +3016,11 @@ void ControlBar::togglePurchaseScience() hidePurchaseScience(); } +Bool ControlBar::isPurchaseScienceVisible() const +{ + return m_contextParent[ CP_PURCHASE_SCIENCE ] && !m_contextParent[ CP_PURCHASE_SCIENCE ]->winIsHidden(); +} + void ControlBar::toggleControlBarStage() { if(m_currentControlBarStage == CONTROL_BAR_STAGE_DEFAULT ) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index bc2a6eeeeca..353d22d7cfd 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -1244,20 +1244,12 @@ const char * DX8Wrapper::Get_Render_Device_Name(int device_index) bool DX8Wrapper::Set_Device_Resolution(int width,int height,int bits,int windowed, bool resize_window) { if (D3DDevice != nullptr) { + int w = (width == -1) ? ResolutionWidth : width; + int h = (height == -1) ? ResolutionHeight : height; + int b = (bits == -1) ? BitDepth : bits; + int win = (windowed == -1) ? IsWindowed : windowed; - if (width != -1) { - _PresentParameters.BackBufferWidth = ResolutionWidth = width; - } - if (height != -1) { - _PresentParameters.BackBufferHeight = ResolutionHeight = height; - } - if (resize_window) - { - Resize_And_Position_Window(); - } -#pragma message("TODO: support changing windowed status and changing the bit depth") - WWDEBUG_SAY(("DX8Wrapper::Set_Device_Resolution is resetting the device.")); - return Reset_Device(); + return Set_Render_Device(CurRenderDevice, w, h, b, win, resize_window, true, true); } else { return false; } diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index 7f484111672..b71b29a9a3f 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -54,7 +54,7 @@ constexpr const Int MAX_GLOBAL_LIGHTS = 3; constexpr const Int SIMULATE_REPLAYS_SEQUENTIAL = -1; //------------------------------------------------------------------------------------------------- -class CommandLineData +struct CommandLineData { friend class CommandLine; friend class GlobalData; @@ -62,10 +62,12 @@ class CommandLineData CommandLineData() : m_hasParsedCommandLineForStartup(false) , m_hasParsedCommandLineForEngineInit(false) + , m_windowedCommandLineSpecified(false) {} Bool m_hasParsedCommandLineForStartup; Bool m_hasParsedCommandLineForEngineInit; + Bool m_windowedCommandLineSpecified; }; //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/GameWindowManager.h b/GeneralsMD/Code/GameEngine/Include/GameClient/GameWindowManager.h index e976c6f5735..bb19a3b3c45 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/GameWindowManager.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/GameWindowManager.h @@ -65,6 +65,19 @@ class WindowLayoutInfo std::list windows; ///< list of top-level windows in the layout }; +struct WindowLayoutRules +{ + Int unscaledScreenX; + Int unscaledScreenY; + Int unscaledScreenWidth; + Int unscaledScreenHeight; + Int createResX; + Int createResY; +}; + +extern std::map g_windowLayoutRegistry; + + //------------------------------------------------------------------------------------------------- /** There exists a singleton GameWindowManager that defines how we can * interact with the game windowing system */ diff --git a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp index 772830f0f67..9407839792f 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp @@ -29,6 +29,7 @@ #include "Common/CommandLine.h" #include "Common/CRCDebug.h" #include "Common/LocalFileSystem.h" +#include "Common/OptionPreferences.h" #include "Common/Recorder.h" #include "Common/version.h" #include "GameClient/ClientInstance.h" @@ -118,6 +119,7 @@ Int parseNoLogOrCrash(char *args[], int) Int parseWin(char *args[], int) { TheWritableGlobalData->m_windowed = true; + TheWritableGlobalData->m_commandLineData.m_windowedCommandLineSpecified = true; return 1; } @@ -377,6 +379,7 @@ Int parseNoAudio(char *args[], int) Int parseNoWin(char *args[], int) { TheWritableGlobalData->m_windowed = false; + TheWritableGlobalData->m_commandLineData.m_windowedCommandLineSpecified = true; return 1; } @@ -1435,6 +1438,9 @@ void CommandLine::parseCommandLineForStartup() return; TheWritableGlobalData->m_commandLineData.m_hasParsedCommandLineForStartup = true; + OptionPreferences optionPref; + TheWritableGlobalData->m_windowed = optionPref.getWindowed(); + parseCommandLine(paramsForStartup, ARRAY_SIZE(paramsForStartup)); } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp index e862cd149d5..8b1bfdfdeb9 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp @@ -1176,6 +1176,8 @@ void GlobalData::reset() //------------------------------------------------------------------------------------------------- void GlobalData::parseGameDataDefinition( INI* ini ) { + Bool cmdWindowed = TheGlobalData->m_windowed; + if( TheWritableGlobalData && ini->getLoadType() != INI_LOAD_MULTIFILE) { @@ -1227,6 +1229,15 @@ void GlobalData::parseGameDataDefinition( INI* ini ) TheWritableGlobalData->m_showMoneyPerMinute = optionPref.getShowMoneyPerMinute(); TheWritableGlobalData->m_gameWindowTransitionSpeedMultiplier = optionPref.getGameWindowTransitionSpeedMultiplier(); + if (TheGlobalData->m_commandLineData.m_windowedCommandLineSpecified) + { + TheWritableGlobalData->m_windowed = cmdWindowed; + } + else + { + TheWritableGlobalData->m_windowed = optionPref.getWindowed(); + } + TheWritableGlobalData->m_antiAliasLevel = optionPref.getAntiAliasing(); TheWritableGlobalData->m_textureFilteringMode = optionPref.getTextureFilterMode(); TheWritableGlobalData->m_textureAnisotropyLevel = optionPref.getTextureAnisotropyLevel(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp index 5fa7a3f8a6c..33213b95e2c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp @@ -1407,9 +1407,6 @@ void OptionsMenuInit( WindowLayout *layout, void *userData ) if (comboBoxDetail) comboBoxDetail->winEnable(FALSE); - if (comboBoxResolution) - comboBoxResolution->winEnable(FALSE); - if (textEntryFirewallPortOverride) textEntryFirewallPortOverride->winEnable(FALSE); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp index 773c22dd94e..d2353fd02cd 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp @@ -1403,6 +1403,8 @@ Int GameWindowManager::winDestroy( GameWindow *window ) if( window == nullptr ) return WIN_ERR_INVALID_WINDOW; + g_windowLayoutRegistry.erase(window); + // // we should never have edit data allocated in the window code, it's // completely handled by the editor ONLY @@ -4063,5 +4065,53 @@ GameWindowDummy::~GameWindowDummy() { } +void reflowWindowTree(GameWindow *win, Int newScreenWidth, Int newScreenHeight) +{ + while (win) + { + std::map::iterator it = g_windowLayoutRegistry.find(win); + if (it != g_windowLayoutRegistry.end()) + { + const WindowLayoutRules &rules = it->second; + + Int newScreenX = (Int)(rules.unscaledScreenX * ((Real)newScreenWidth / (Real)rules.createResX)); + Int newScreenY = (Int)(rules.unscaledScreenY * ((Real)newScreenHeight / (Real)rules.createResY)); + Int newWidth = (Int)(rules.unscaledScreenWidth * ((Real)newScreenWidth / (Real)rules.createResX)); + Int newHeight = (Int)(rules.unscaledScreenHeight * ((Real)newScreenHeight / (Real)rules.createResY)); + + Int newRelX = newScreenX; + Int newRelY = newScreenY; + + GameWindow *parent = win->winGetParent(); + if (parent) + { + Int parentScreenX = 0, parentScreenY = 0; + parent->winGetScreenPosition(&parentScreenX, &parentScreenY); + newRelX = newScreenX - parentScreenX; + newRelY = newScreenY - parentScreenY; + } + + win->winSetPosition(newRelX, newRelY); + win->winSetSize(newWidth, newHeight); + } + + if (win->winGetChild()) + { + reflowWindowTree(win->winGetChild(), newScreenWidth, newScreenHeight); + } + + win = win->winGetNext(); + } +} + +void reflowAllWindows(Int newScreenWidth, Int newScreenHeight) +{ + if (TheWindowManager) + { + GameWindow *head = TheWindowManager->winGetWindowList(); + reflowWindowTree(head, newScreenWidth, newScreenHeight); + } +} + diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp index d524365f132..09d074018e0 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp @@ -71,8 +71,22 @@ #include "GameClient/GadgetSlider.h" #include "GameClient/GameText.h" #include "GameClient/HeaderTemplate.h" +#include +std::map g_windowLayoutRegistry; +struct TempParsedRect +{ + Int x; + Int y; + Int w; + Int h; + Int resX; + Int resY; + Bool valid; +}; + +static TempParsedRect g_tempParsedRect = { 0, 0, 0, 0, 0, 0, FALSE }; // DEFINES //////////////////////////////////////////////////////////////////// @@ -520,6 +534,13 @@ static Bool parseScreenRect( const char *token, char *buffer, scanInt( c, createRes.x ); c = strtok( nullptr, seps ); // y creation resolution scanInt( c, createRes.y ); + g_tempParsedRect.x = screenRegion.lo.x; + g_tempParsedRect.y = screenRegion.lo.y; + g_tempParsedRect.w = screenRegion.hi.x - screenRegion.lo.x; + g_tempParsedRect.h = screenRegion.hi.y - screenRegion.lo.y; + g_tempParsedRect.resX = createRes.x; + g_tempParsedRect.resY = createRes.y; + g_tempParsedRect.valid = TRUE; // // shrink or expand the screen region by the ratio of the current @@ -2129,6 +2150,19 @@ static GameWindow *createWindow( char *type, if( window && parent ) TheWindowManager->winSendInputMsg( parent, GWM_SCRIPT_CREATE, id, 0 ); + if (window && g_tempParsedRect.valid) + { + WindowLayoutRules rules; + rules.unscaledScreenX = g_tempParsedRect.x; + rules.unscaledScreenY = g_tempParsedRect.y; + rules.unscaledScreenWidth = g_tempParsedRect.w; + rules.unscaledScreenHeight = g_tempParsedRect.h; + rules.createResX = g_tempParsedRect.resX; + rules.createResY = g_tempParsedRect.resY; + g_windowLayoutRegistry[window] = rules; + g_tempParsedRect.valid = FALSE; + } + return window; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index b3927ee92bd..9301be93824 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -6001,8 +6001,25 @@ void InGameUI::resetIdleWorker() void InGameUI::recreateControlBar() { - GameWindow *win = TheWindowManager->winGetWindowFromId(nullptr, TheNameKeyGenerator->nameToKey("ControlBar.wnd")); - deleteInstance(win); + GameWindow *win = TheWindowManager->winGetWindowFromId(nullptr, TheNameKeyGenerator->nameToKey("ControlBar.wnd:ControlBarParent")); + if (!win) + { + return; + } + + Bool wasHidden = win->winIsHidden(); + Bool wasScienceVisible = FALSE; + + if (TheControlBar) + { + if (TheControlBar->isPurchaseScienceVisible()) + { + wasScienceVisible = TRUE; + } + TheControlBar->showRallyPoint(nullptr); + } + + TheWindowManager->winDestroy(win); m_idleWorkerWin = nullptr; @@ -6011,6 +6028,30 @@ void InGameUI::recreateControlBar() delete TheControlBar; TheControlBar = NEW ControlBar; TheControlBar->init(); + + if (ThePlayerList) + { + Player *localPlayer = ThePlayerList->getLocalPlayer(); + if (localPlayer) + { + TheControlBar->setControlBarSchemeByPlayer(localPlayer); + TheControlBar->initSpecialPowershortcutBar(localPlayer); + } + } + + if (wasHidden) + { + HideControlBar(TRUE); + } + else + { + ShowControlBar(TRUE); + } + + if (wasScienceVisible && TheControlBar) + { + TheControlBar->showPurchaseScience(); + } } void InGameUI::refreshCustomUiResources() diff --git a/GeneralsMD/Code/GameEngineDevice/Source/Win32Device/Common/Win32GameEngine.cpp b/GeneralsMD/Code/GameEngineDevice/Source/Win32Device/Common/Win32GameEngine.cpp index 485d5f83bd2..fefb355c665 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/Win32Device/Common/Win32GameEngine.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/Win32Device/Common/Win32GameEngine.cpp @@ -123,6 +123,9 @@ void Win32GameEngine::update() // allow windows to perform regular windows maintenance stuff like msgs serviceWindowsOS(); + extern void checkAndApplyDeferredResize(); + checkAndApplyDeferredResize(); + } //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 0d37cab5933..0120752e4b2 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -30,7 +30,12 @@ // /////////////////////////////////////////////////////////////////////////////// -// SYSTEM INCLUDES //////////////////////////////////////////////////////////// +#ifndef WINVER +#define WINVER 0x0500 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0500 +#endif #define WIN32_LEAN_AND_MEAN // only bare bones windows stuff wanted #include #include @@ -51,15 +56,20 @@ #include "Common/GameMemory.h" #include "Common/StackDump.h" #include "Common/MessageStream.h" +#include "Common/OptionPreferences.h" #include "Common/PlayerList.h" #include "Common/Registry.h" #include "Common/Team.h" #include "GameClient/ClientInstance.h" +#include "GameClient/Display.h" #include "GameClient/InGameUI.h" #include "GameClient/GameClient.h" #include "GameLogic/GameLogic.h" ///< @todo for demo, remove +#include "GameClient/GameWindowTransitions.h" +#include "GameClient/HeaderTemplate.h" #include "GameClient/Mouse.h" #include "GameClient/IMEManager.h" +#include "GameClient/View.h" #include "Win32Device/GameClient/Win32Mouse.h" #include "Win32Device/Common/Win32GameEngine.h" #include "Common/version.h" @@ -290,6 +300,91 @@ static const char *messageToString(unsigned int message) } #endif +volatile bool g_inInternalResize = false; +volatile bool g_resizePending = false; +static bool g_inSizeMove = false; +static Int g_savedWindowedWidth = 800; +static Int g_savedWindowedHeight = 600; + +struct InternalResizeGuard +{ + InternalResizeGuard() { g_inInternalResize = true; } + ~InternalResizeGuard() { g_inInternalResize = false; } +}; + +extern void reflowAllWindows(Int newScreenWidth, Int newScreenHeight); + +static void performLiveResize(HWND hWnd) +{ + InternalResizeGuard guard; + if (gInitializing) + { + return; + } + + RECT rect; + if (GetClientRect(hWnd, &rect)) + { + Int newWidth = rect.right - rect.left; + Int newHeight = rect.bottom - rect.top; + + if (newWidth <= 0 || newHeight <= 0) + { + return; + } + + bool windowedChanged = (TheDisplay && TheDisplay->getWindowed() != TheGlobalData->m_windowed); + if (TheGlobalData && (newWidth != TheGlobalData->m_xResolution || newHeight != TheGlobalData->m_yResolution || windowedChanged)) + { + if (TheDisplay && TheDisplay->setDisplayMode(newWidth, newHeight, TheDisplay->getBitDepth(), TheGlobalData->m_windowed)) + { + if (TheWritableGlobalData) + { + TheWritableGlobalData->m_xResolution = newWidth; + TheWritableGlobalData->m_yResolution = newHeight; + } + + if (TheGlobalData->m_windowed) + { + g_savedWindowedWidth = newWidth; + g_savedWindowedHeight = newHeight; + } + + if (TheTacticalView) + { + TheTacticalView->setWidth(newWidth); + TheTacticalView->setHeight(newHeight); + } + + if (TheHeaderTemplateManager) + TheHeaderTemplateManager->onResolutionChanged(); + + if (TheMouse) + TheMouse->onResolutionChanged(); + + if (TheTransitionHandler) + TheTransitionHandler->reset(); + + reflowAllWindows(newWidth, newHeight); + + if (TheInGameUI) + { + TheInGameUI->recreateControlBar(); + } + } + } + } +} + +void checkAndApplyDeferredResize() +{ + if (g_resizePending) + { + g_resizePending = false; + performLiveResize(ApplicationHWnd); + } +} + // WndProc ==================================================================== /** Window Procedure */ //============================================================================= @@ -359,11 +454,18 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, return 1; case SC_MOVE: case SC_SIZE: - case SC_MAXIMIZE: case SC_MONITORPOWER: if( !TheGlobalData->m_windowed ) return 1; break; + case SC_MAXIMIZE: + if( !TheGlobalData->m_windowed ) + return 1; + else + { + SendMessage(hWnd, WM_SYSKEYDOWN, VK_RETURN, 1 << 29); + return 0; + } } break; @@ -410,6 +512,18 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, break; } + case WM_ENTERSIZEMOVE: + g_inSizeMove = true; + break; + + case WM_EXITSIZEMOVE: + g_inSizeMove = false; + if (!g_inInternalResize) + { + g_resizePending = true; + } + break; + //------------------------------------------------------------------------- case WM_SIZE: { @@ -420,6 +534,11 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, if (TheMouse) TheMouse->refreshCursorCapture(); + if (!g_inInternalResize && !g_inSizeMove && wParam != SIZE_MINIMIZED) + { + g_resizePending = true; + } + break; } @@ -662,6 +781,67 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, break; } #endif + case WM_SYSKEYDOWN: + { + if (wParam == VK_RETURN && (lParam & (1 << 29)) && !(lParam & (1 << 30))) + { + if (TheGameEngine && !TheGameEngine->getQuitting() && TheDisplay) + { + TheWritableGlobalData->m_windowed = !TheGlobalData->m_windowed; + + DWORD windowStyle = WS_POPUP | WS_VISIBLE; + DWORD exStyle = 0; + Int resX = 0; + Int resY = 0; + + MONITORINFO mi = { sizeof(MONITORINFO) }; + GetMonitorInfo(MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST), &mi); + + if (TheGlobalData->m_windowed) + { + windowStyle |= WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU | WS_THICKFRAME | WS_CAPTION; + resX = g_savedWindowedWidth; + resY = g_savedWindowedHeight; + } + else + { + windowStyle |= WS_SYSMENU; + exStyle |= WS_EX_TOPMOST; + resX = mi.rcMonitor.right - mi.rcMonitor.left; + resY = mi.rcMonitor.bottom - mi.rcMonitor.top; + } + + RECT windowRect = { 0, 0, resX, resY }; + AdjustWindowRect(&windowRect, windowStyle, FALSE); + int width = windowRect.right - windowRect.left; + int height = windowRect.bottom - windowRect.top; + + int x = 0, y = 0; + if (TheGlobalData->m_windowed) + { + x = max(mi.rcWork.left, mi.rcWork.left + (mi.rcWork.right - mi.rcWork.left - width) / 2); + y = max(mi.rcWork.top, mi.rcWork.top + (mi.rcWork.bottom - mi.rcWork.top - height) / 2); + } + else + { + x = mi.rcMonitor.left; + y = mi.rcMonitor.top; + } + + OptionPreferences optionPref; + optionPref.setWindowed(TheGlobalData->m_windowed); + optionPref.write(); + + SetWindowLong(hWnd, GWL_STYLE, windowStyle); + SetWindowLong(hWnd, GWL_EXSTYLE, exStyle); + SetWindowPos(hWnd, TheGlobalData->m_windowed ? HWND_NOTOPMOST : HWND_TOPMOST, + x, y, width, height, SWP_FRAMECHANGED | SWP_SHOWWINDOW); + UpdateWindow(hWnd); + } + return 0; + } + break; + } } } @@ -711,7 +891,7 @@ static Bool initializeAppWindows( HINSTANCE hInstance, Int nCmdShow, Bool runWin // Create our main window windowStyle = WS_POPUP|WS_VISIBLE; if (runWindowed) - windowStyle |= WS_MINIMIZEBOX | WS_SYSMENU | WS_DLGFRAME | WS_CAPTION; + windowStyle |= WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU | WS_THICKFRAME | WS_CAPTION; else windowStyle |= WS_EX_TOPMOST | WS_SYSMENU; From c3b1b9e67054d506df87568ffc767c423d722664 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Sat, 27 Jun 2026 21:10:17 +0200 Subject: [PATCH 2/7] fix(winmain): resolve template type ambiguity for max() under VC6 --- GeneralsMD/Code/Main/WinMain.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 0120752e4b2..b40ab8f5a53 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -813,10 +813,10 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, RECT windowRect = { 0, 0, resX, resY }; AdjustWindowRect(&windowRect, windowStyle, FALSE); - int width = windowRect.right - windowRect.left; - int height = windowRect.bottom - windowRect.top; + LONG width = windowRect.right - windowRect.left; + LONG height = windowRect.bottom - windowRect.top; - int x = 0, y = 0; + LONG x = 0, y = 0; if (TheGlobalData->m_windowed) { x = max(mi.rcWork.left, mi.rcWork.left + (mi.rcWork.right - mi.rcWork.left - width) / 2); From 3b43c1487092972352bb20669e240a31c8515fd8 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Sat, 27 Jun 2026 23:55:40 +0200 Subject: [PATCH 3/7] feature(winmain): support F11 for fullscreen toggle --- GeneralsMD/Code/Main/WinMain.cpp | 121 +++++++++++++++++-------------- 1 file changed, 67 insertions(+), 54 deletions(-) diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index b40ab8f5a53..4db1801a896 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -385,6 +385,63 @@ void checkAndApplyDeferredResize() } } +static void toggleFullscreen(HWND hWnd) +{ + if (TheGameEngine && !TheGameEngine->getQuitting() && TheDisplay) + { + TheWritableGlobalData->m_windowed = !TheGlobalData->m_windowed; + + DWORD windowStyle = WS_POPUP | WS_VISIBLE; + DWORD exStyle = 0; + Int resX = 0; + Int resY = 0; + + MONITORINFO mi = { sizeof(MONITORINFO) }; + GetMonitorInfo(MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST), &mi); + + if (TheGlobalData->m_windowed) + { + windowStyle |= WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU | WS_THICKFRAME | WS_CAPTION; + resX = g_savedWindowedWidth; + resY = g_savedWindowedHeight; + } + else + { + windowStyle |= WS_SYSMENU; + exStyle |= WS_EX_TOPMOST; + resX = mi.rcMonitor.right - mi.rcMonitor.left; + resY = mi.rcMonitor.bottom - mi.rcMonitor.top; + } + + RECT windowRect = { 0, 0, resX, resY }; + AdjustWindowRect(&windowRect, windowStyle, FALSE); + LONG width = windowRect.right - windowRect.left; + LONG height = windowRect.bottom - windowRect.top; + + LONG x = 0, y = 0; + if (TheGlobalData->m_windowed) + { + x = max(mi.rcWork.left, mi.rcWork.left + (mi.rcWork.right - mi.rcWork.left - width) / 2); + y = max(mi.rcWork.top, mi.rcWork.top + (mi.rcWork.bottom - mi.rcWork.top - height) / 2); + } + else + { + x = mi.rcMonitor.left; + y = mi.rcMonitor.top; + } + + OptionPreferences optionPref; + optionPref.setWindowed(TheGlobalData->m_windowed); + optionPref.write(); + + SetWindowLong(hWnd, GWL_STYLE, windowStyle); + SetWindowLong(hWnd, GWL_EXSTYLE, exStyle); + SetWindowPos(hWnd, TheGlobalData->m_windowed ? HWND_NOTOPMOST : HWND_TOPMOST, + x, y, width, height, SWP_FRAMECHANGED | SWP_SHOWWINDOW); + UpdateWindow(hWnd); + } +} + // WndProc ==================================================================== /** Window Procedure */ //============================================================================= @@ -463,7 +520,7 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, return 1; else { - SendMessage(hWnd, WM_SYSKEYDOWN, VK_RETURN, 1 << 29); + toggleFullscreen(hWnd); return 0; } } @@ -638,6 +695,14 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, PostQuitMessage( 0 ); break; } + case VK_F11: + { + if (!(lParam & (1 << 30))) + { + toggleFullscreen(hWnd); + } + break; + } } return 0; } @@ -785,59 +850,7 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, { if (wParam == VK_RETURN && (lParam & (1 << 29)) && !(lParam & (1 << 30))) { - if (TheGameEngine && !TheGameEngine->getQuitting() && TheDisplay) - { - TheWritableGlobalData->m_windowed = !TheGlobalData->m_windowed; - - DWORD windowStyle = WS_POPUP | WS_VISIBLE; - DWORD exStyle = 0; - Int resX = 0; - Int resY = 0; - - MONITORINFO mi = { sizeof(MONITORINFO) }; - GetMonitorInfo(MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST), &mi); - - if (TheGlobalData->m_windowed) - { - windowStyle |= WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU | WS_THICKFRAME | WS_CAPTION; - resX = g_savedWindowedWidth; - resY = g_savedWindowedHeight; - } - else - { - windowStyle |= WS_SYSMENU; - exStyle |= WS_EX_TOPMOST; - resX = mi.rcMonitor.right - mi.rcMonitor.left; - resY = mi.rcMonitor.bottom - mi.rcMonitor.top; - } - - RECT windowRect = { 0, 0, resX, resY }; - AdjustWindowRect(&windowRect, windowStyle, FALSE); - LONG width = windowRect.right - windowRect.left; - LONG height = windowRect.bottom - windowRect.top; - - LONG x = 0, y = 0; - if (TheGlobalData->m_windowed) - { - x = max(mi.rcWork.left, mi.rcWork.left + (mi.rcWork.right - mi.rcWork.left - width) / 2); - y = max(mi.rcWork.top, mi.rcWork.top + (mi.rcWork.bottom - mi.rcWork.top - height) / 2); - } - else - { - x = mi.rcMonitor.left; - y = mi.rcMonitor.top; - } - - OptionPreferences optionPref; - optionPref.setWindowed(TheGlobalData->m_windowed); - optionPref.write(); - - SetWindowLong(hWnd, GWL_STYLE, windowStyle); - SetWindowLong(hWnd, GWL_EXSTYLE, exStyle); - SetWindowPos(hWnd, TheGlobalData->m_windowed ? HWND_NOTOPMOST : HWND_TOPMOST, - x, y, width, height, SWP_FRAMECHANGED | SWP_SHOWWINDOW); - UpdateWindow(hWnd); - } + toggleFullscreen(hWnd); return 0; } break; From f755bf9c768524927f39e56266c0392e5a24c00d Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Sun, 28 Jun 2026 00:47:47 +0200 Subject: [PATCH 4/7] Fix window size restoration and deferred resize hang during loading --- GeneralsMD/Code/Main/WinMain.cpp | 62 +++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 4db1801a896..126a4dcd7cc 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -69,6 +69,7 @@ #include "GameClient/HeaderTemplate.h" #include "GameClient/Mouse.h" #include "GameClient/IMEManager.h" +#include "GameClient/Shell.h" #include "GameClient/View.h" #include "Win32Device/GameClient/Win32Mouse.h" #include "Win32Device/Common/Win32GameEngine.h" @@ -333,10 +334,11 @@ static void performLiveResize(HWND hWnd) return; } - bool windowedChanged = (TheDisplay && TheDisplay->getWindowed() != TheGlobalData->m_windowed); + bool desiredD3DWindowed = TheDisplay ? (TheDisplay->getWindowed() || TheGlobalData->m_windowed) : TheGlobalData->m_windowed; + bool windowedChanged = (TheDisplay && TheDisplay->getWindowed() != desiredD3DWindowed); if (TheGlobalData && (newWidth != TheGlobalData->m_xResolution || newHeight != TheGlobalData->m_yResolution || windowedChanged)) { - if (TheDisplay && TheDisplay->setDisplayMode(newWidth, newHeight, TheDisplay->getBitDepth(), TheGlobalData->m_windowed)) + if (TheDisplay && TheDisplay->setDisplayMode(newWidth, newHeight, TheDisplay->getBitDepth(), desiredD3DWindowed)) { if (TheWritableGlobalData) { @@ -371,14 +373,44 @@ static void performLiveResize(HWND hWnd) { TheInGameUI->recreateControlBar(); } + + OptionPreferences optionPref; + optionPref.setWindowed(TheGlobalData->m_windowed); + AsciiString resString; + resString.format("%d %d", newWidth, newHeight); + optionPref.setAsciiString("Resolution", resString); + optionPref.write(); } } } } +static bool isResizeSafe() +{ + if (!TheGameEngine || TheGameEngine->getQuitting()) + { + return false; + } + + if (TheShell && TheShell->getScreenCount() > 0) + { + if (TheShell->isAnimFinished()) + { + return true; + } + } + + if (TheGameLogic && TheGameLogic->isInGame() && !TheGameLogic->isInShellGame()) + { + return true; + } + + return false; +} + void checkAndApplyDeferredResize() { - if (g_resizePending) + if (g_resizePending && isResizeSafe()) { g_resizePending = false; performLiveResize(ApplicationHWnd); @@ -387,6 +419,11 @@ void checkAndApplyDeferredResize() static void toggleFullscreen(HWND hWnd) { + if (!isResizeSafe()) + { + return; + } + if (TheGameEngine && !TheGameEngine->getQuitting() && TheDisplay) { TheWritableGlobalData->m_windowed = !TheGlobalData->m_windowed; @@ -430,10 +467,6 @@ static void toggleFullscreen(HWND hWnd) y = mi.rcMonitor.top; } - OptionPreferences optionPref; - optionPref.setWindowed(TheGlobalData->m_windowed); - optionPref.write(); - SetWindowLong(hWnd, GWL_STYLE, windowStyle); SetWindowLong(hWnd, GWL_EXSTYLE, exStyle); SetWindowPos(hWnd, TheGlobalData->m_windowed ? HWND_NOTOPMOST : HWND_TOPMOST, @@ -789,9 +822,9 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, case WM_PAINT: { + PAINTSTRUCT paint; + HDC dc = ::BeginPaint(hWnd, &paint); if (gDoPaint) { - PAINTSTRUCT paint; - HDC dc = ::BeginPaint(hWnd, &paint); #if 0 ::SetTextColor(dc, RGB(255,255,255)); ::SetBkColor(dc, RGB(0,0,0)); @@ -806,10 +839,9 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, ::DeleteDC(tmpDC); ::RestoreDC(dc, savContext); } - ::EndPaint(hWnd, &paint); - return TRUE; } - break; + ::EndPaint(hWnd, &paint); + return TRUE; } case WM_ERASEBKGND: @@ -892,6 +924,12 @@ static Bool initializeAppWindows( HINSTANCE hInstance, Int nCmdShow, Bool runWin Int startWidth = DEFAULT_DISPLAY_WIDTH, startHeight = DEFAULT_DISPLAY_HEIGHT; + if (TheGlobalData) + { + g_savedWindowedWidth = TheGlobalData->m_xResolution; + g_savedWindowedHeight = TheGlobalData->m_yResolution; + } + // register the window class WNDCLASS wndClass = { CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, WndProc, 0, 0, hInstance, From 8b1dfe9c295ac84f6bcf63bb80b7f4ef9f79b190 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Sun, 28 Jun 2026 00:50:56 +0200 Subject: [PATCH 5/7] Fix template type deduction ambiguity for VC6 compilation on CI --- GeneralsMD/Code/Main/WinMain.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 126a4dcd7cc..bbc535fc12b 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -458,8 +458,10 @@ static void toggleFullscreen(HWND hWnd) LONG x = 0, y = 0; if (TheGlobalData->m_windowed) { - x = max(mi.rcWork.left, mi.rcWork.left + (mi.rcWork.right - mi.rcWork.left - width) / 2); - y = max(mi.rcWork.top, mi.rcWork.top + (mi.rcWork.bottom - mi.rcWork.top - height) / 2); + LONG targetX = mi.rcWork.left + (mi.rcWork.right - mi.rcWork.left - width) / 2; + LONG targetY = mi.rcWork.top + (mi.rcWork.bottom - mi.rcWork.top - height) / 2; + x = (mi.rcWork.left > targetX) ? mi.rcWork.left : targetX; + y = (mi.rcWork.top > targetY) ? mi.rcWork.top : targetY; } else { From 2548636de595f781aeda58a25dbe7913500b4431 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Sun, 28 Jun 2026 01:40:45 +0200 Subject: [PATCH 6/7] Fix windowed preference saving and prevent zero division crash on malformed WND files --- Core/GameEngine/Source/Common/OptionPreferences.cpp | 2 +- .../Source/GameClient/GUI/GameWindowManager.cpp | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Core/GameEngine/Source/Common/OptionPreferences.cpp b/Core/GameEngine/Source/Common/OptionPreferences.cpp index ff042cfae40..b7fe51bbedf 100644 --- a/Core/GameEngine/Source/Common/OptionPreferences.cpp +++ b/Core/GameEngine/Source/Common/OptionPreferences.cpp @@ -776,7 +776,7 @@ Bool OptionPreferences::getWindowed() const void OptionPreferences::setWindowed(Bool windowed) { - insert(std::make_pair(AsciiString("Windowed"), AsciiString(windowed ? "yes" : "no"))); + setBool("Windowed", windowed); } Real OptionPreferences::getMusicVolume() diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp index d2353fd02cd..82bcca57b51 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp @@ -4074,10 +4074,13 @@ void reflowWindowTree(GameWindow *win, Int newScreenWidth, Int newScreenHeight) { const WindowLayoutRules &rules = it->second; - Int newScreenX = (Int)(rules.unscaledScreenX * ((Real)newScreenWidth / (Real)rules.createResX)); - Int newScreenY = (Int)(rules.unscaledScreenY * ((Real)newScreenHeight / (Real)rules.createResY)); - Int newWidth = (Int)(rules.unscaledScreenWidth * ((Real)newScreenWidth / (Real)rules.createResX)); - Int newHeight = (Int)(rules.unscaledScreenHeight * ((Real)newScreenHeight / (Real)rules.createResY)); + Real scaleX = (rules.createResX > 0) ? ((Real)newScreenWidth / (Real)rules.createResX) : 1.0f; + Real scaleY = (rules.createResY > 0) ? ((Real)newScreenHeight / (Real)rules.createResY) : 1.0f; + + Int newScreenX = (Int)(rules.unscaledScreenX * scaleX); + Int newScreenY = (Int)(rules.unscaledScreenY * scaleY); + Int newWidth = (Int)(rules.unscaledScreenWidth * scaleX); + Int newHeight = (Int)(rules.unscaledScreenHeight * scaleY); Int newRelX = newScreenX; Int newRelY = newScreenY; From f14015732295e62fb86c639831e27d4876867063 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Thu, 30 Jul 2026 23:55:40 +0200 Subject: [PATCH 7/7] fix(winmain): clear deferred resize flag only on successful resize --- GeneralsMD/Code/Main/WinMain.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index bbc535fc12b..b435d52fd6d 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -315,12 +315,12 @@ struct InternalResizeGuard extern void reflowAllWindows(Int newScreenWidth, Int newScreenHeight); -static void performLiveResize(HWND hWnd) +static bool performLiveResize(HWND hWnd) { InternalResizeGuard guard; if (gInitializing) { - return; + return false; } RECT rect; @@ -331,7 +331,7 @@ static void performLiveResize(HWND hWnd) if (newWidth <= 0 || newHeight <= 0) { - return; + return false; } bool desiredD3DWindowed = TheDisplay ? (TheDisplay->getWindowed() || TheGlobalData->m_windowed) : TheGlobalData->m_windowed; @@ -380,9 +380,14 @@ static void performLiveResize(HWND hWnd) resString.format("%d %d", newWidth, newHeight); optionPref.setAsciiString("Resolution", resString); optionPref.write(); + + return true; } + return false; } + return true; } + return false; } static bool isResizeSafe() @@ -412,8 +417,10 @@ void checkAndApplyDeferredResize() { if (g_resizePending && isResizeSafe()) { - g_resizePending = false; - performLiveResize(ApplicationHWnd); + if (performLiveResize(ApplicationHWnd)) + { + g_resizePending = false; + } } }